mirror of https://gitlab.federez.net/re2o/re2o
126 changed files with 9407 additions and 2068 deletions
@ -0,0 +1,26 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-15 18:33 |
|||
from __future__ import unicode_literals |
|||
|
|||
import django.core.validators |
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('cotisations', '0023_auto_20170902_1303'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='article', |
|||
name='duration', |
|||
field=models.PositiveIntegerField(blank=True, help_text='Durée exprimée en mois entiers', null=True, validators=[django.core.validators.MinValueValidator(0)]), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='vente', |
|||
name='duration', |
|||
field=models.PositiveIntegerField(blank=True, help_text='Durée exprimée en mois entiers', null=True), |
|||
), |
|||
] |
|||
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-27 03:02 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('cotisations', '0024_auto_20171015_2033'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AddField( |
|||
model_name='article', |
|||
name='type_user', |
|||
field=models.CharField(choices=[('Adherent', 'Adherent'), ('Club', 'Club'), ('All', 'All')], default='All', max_length=255), |
|||
), |
|||
] |
|||
@ -0,0 +1,78 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-27 23:26 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
def create_type(apps, schema_editor): |
|||
Cotisation = apps.get_model('cotisations', 'Cotisation') |
|||
Vente = apps.get_model('cotisations', 'Vente') |
|||
Article = apps.get_model('cotisations', 'Article') |
|||
db_alias = schema_editor.connection.alias |
|||
articles = Article.objects.using(db_alias).all() |
|||
ventes = Vente.objects.using(db_alias).all() |
|||
cotisations = Cotisation.objects.using(db_alias).all() |
|||
for article in articles: |
|||
if article.iscotisation: |
|||
article.type_cotisation='All' |
|||
article.save(using=db_alias) |
|||
for vente in ventes: |
|||
if vente.iscotisation: |
|||
vente.type_cotisation='All' |
|||
vente.save(using=db_alias) |
|||
for cotisation in cotisations: |
|||
cotisation.type_cotisation='All' |
|||
cotisation.save(using=db_alias) |
|||
|
|||
def delete_type(apps, schema_editor): |
|||
Vente = apps.get_model('cotisations', 'Vente') |
|||
Article = apps.get_model('cotisations', 'Article') |
|||
db_alias = schema_editor.connection.alias |
|||
articles = Articles.objects.using(db_alias).all() |
|||
ventes = Vente.objects.using(db_alias).all() |
|||
for article in articles: |
|||
if article.type_cotisation: |
|||
article.iscotisation=True |
|||
else: |
|||
article.iscotisation=False |
|||
article.save(using=db_alias) |
|||
for vente in ventes: |
|||
if vente.iscotisation: |
|||
vente.iscotisation=True |
|||
else: |
|||
vente.iscotisation=False |
|||
vente.save(using=db_alias) |
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('cotisations', '0025_article_type_user'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AddField( |
|||
model_name='article', |
|||
name='type_cotisation', |
|||
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], default=None, max_length=255, null=True), |
|||
), |
|||
migrations.AddField( |
|||
model_name='cotisation', |
|||
name='type_cotisation', |
|||
field=models.CharField(choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255), |
|||
), |
|||
migrations.AddField( |
|||
model_name='vente', |
|||
name='type_cotisation', |
|||
field=models.CharField(blank=True, choices=[('Connexion', 'Connexion'), ('Adhesion', 'Adhesion'), ('All', 'All')], max_length=255, null=True), |
|||
), |
|||
migrations.RunPython(create_type, delete_type), |
|||
migrations.RemoveField( |
|||
model_name='article', |
|||
name='iscotisation', |
|||
), |
|||
migrations.RemoveField( |
|||
model_name='vente', |
|||
name='iscotisation', |
|||
), |
|||
] |
|||
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-29 10:56 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('cotisations', '0026_auto_20171028_0126'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='article', |
|||
name='name', |
|||
field=models.CharField(max_length=255), |
|||
), |
|||
] |
|||
Binary file not shown.
@ -0,0 +1,21 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-03 16:08 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('machines', '0059_iptype_prefix_v6'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AddField( |
|||
model_name='iptype', |
|||
name='ouverture_ports', |
|||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='machines.OuverturePortList'), |
|||
), |
|||
] |
|||
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-15 18:33 |
|||
from __future__ import unicode_literals |
|||
|
|||
import django.core.validators |
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('machines', '0060_iptype_ouverture_ports'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='mx', |
|||
name='priority', |
|||
field=models.PositiveIntegerField(unique=True), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='ouvertureport', |
|||
name='begin', |
|||
field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)]), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='ouvertureport', |
|||
name='end', |
|||
field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)]), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='vlan', |
|||
name='vlan_id', |
|||
field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(4095)]), |
|||
), |
|||
] |
|||
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-18 14:08 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('machines', '0061_auto_20171015_2033'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AddField( |
|||
model_name='extension', |
|||
name='origin_v6', |
|||
field=models.GenericIPAddressField(blank=True, null=True, protocol='IPv6'), |
|||
), |
|||
] |
|||
@ -0,0 +1,34 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-19 22:40 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
import machines.models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('machines', '0062_extension_origin_v6'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='SOA', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=255)), |
|||
('mail', models.EmailField(help_text='Email du contact pour la zone', max_length=254)), |
|||
('refresh', models.PositiveIntegerField(default=86400, help_text='Secondes avant que les DNS secondaires doivent demander le serial du DNS primaire pour détecter une modification')), |
|||
('retry', models.PositiveIntegerField(default=7200, help_text='Secondes avant que les DNS secondaires fassent une nouvelle demande de serial en cas de timeout du DNS primaire')), |
|||
('expire', models.PositiveIntegerField(default=3600000, help_text='Secondes après lesquelles les DNS secondaires arrêtent de de répondre aux requêtes en cas de timeout du DNS primaire')), |
|||
('ttl', models.PositiveIntegerField(default=172800, help_text='Time To Live')), |
|||
], |
|||
), |
|||
migrations.AddField( |
|||
model_name='extension', |
|||
name='soa', |
|||
field=models.ForeignKey(default=machines.models.SOA.new_default_soa, on_delete=django.db.models.deletion.CASCADE, to='machines.SOA'), |
|||
), |
|||
] |
|||
@ -0,0 +1,56 @@ |
|||
{% comment %} |
|||
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 Gabriel Détraz |
|||
Copyright © 2017 Goulven Kermarec |
|||
Copyright © 2017 Augustin Lemesle |
|||
|
|||
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. |
|||
{% endcomment %} |
|||
|
|||
<table class="table table-striped"> |
|||
<thead> |
|||
<tr> |
|||
<th>Nom</th> |
|||
<th>Mail</th> |
|||
<th>Refresh</th> |
|||
<th>Retry</th> |
|||
<th>Expire</th> |
|||
<th>TTL</th> |
|||
<th></th> |
|||
<th></th> |
|||
</tr> |
|||
</thead> |
|||
{% for soa in soa_list %} |
|||
<tr> |
|||
<td>{{ soa.name }}</td> |
|||
<td>{{ soa.mail }}</td> |
|||
<td>{{ soa.refresh }}</td> |
|||
<td>{{ soa.retry }}</td> |
|||
<td>{{ soa.expire }}</td> |
|||
<td>{{ soa.ttl }}</td> |
|||
<td class="text-right"> |
|||
{% if is_infra %} |
|||
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %} |
|||
{% endif %} |
|||
{% include 'buttons/history.html' with href='machines:history' name='soa' id=soa.id %} |
|||
</td> |
|||
</tr> |
|||
{% endfor %} |
|||
</table> |
|||
|
|||
|
|||
@ -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_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};' |
|||
'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_name ), |
|||
datasets = default_datasets( f_name, match_func ), |
|||
updater = typeahead_updater( f_name ), |
|||
change = typeahead_change( f_name ), |
|||
updates = ''.join( [ ( |
|||
'$( "#{u_id}" ).change( function() {{' |
|||
'setup_{f_name}();' |
|||
'{reset_input}' |
|||
'}} );' |
|||
).format( |
|||
u_id = u_id, |
|||
reset_input = reset_input( f_name ), |
|||
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_name ), |
|||
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_name ) |
|||
) |
|||
|
|||
def reset_input( f_name ) : |
|||
""" The JS script to reset the fields values """ |
|||
return ( |
|||
'$( "#{input_id}" ).typeahead("val", "");' |
|||
'$( "#{hidden_id}" ).val( "" );' |
|||
).format( |
|||
input_id = input_id( f_name ), |
|||
hidden_id = hidden_id( f_name ) |
|||
) |
|||
|
|||
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_name ): |
|||
""" 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_name ) |
|||
) |
|||
|
|||
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}" ).typeahead( "val" ) === "" ) {{' |
|||
'$( "#{hidden_id}" ).val( "" );' |
|||
'$( "#{hidden_id}" ).change();' |
|||
'}}' |
|||
'}}' |
|||
).format( |
|||
input_id = input_id( f_name ), |
|||
hidden_id = hidden_id( f_name ) |
|||
) |
|||
|
|||
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-15 15:41 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('preferences', '0020_optionalmachine_ipv6'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='optionaltopologie', |
|||
name='radius_general_policy', |
|||
field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32), |
|||
), |
|||
] |
|||
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-15 15:58 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('preferences', '0021_auto_20171015_1741'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='optionaltopologie', |
|||
name='radius_general_policy', |
|||
field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32), |
|||
), |
|||
] |
|||
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-15 18:33 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('preferences', '0022_auto_20171015_1758'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='optionaltopologie', |
|||
name='radius_general_policy', |
|||
field=models.CharField(choices=[('MACHINE', 'Sur le vlan de la plage ip machine'), ('DEFINED', 'Prédéfini dans "Vlan où placer les machines après acceptation RADIUS"')], default='DEFINED', max_length=32), |
|||
), |
|||
] |
|||
@ -0,0 +1,809 @@ |
|||
# -*- 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. |
|||
|
|||
""" Templatetag used to render massive django form selects into bootstrap |
|||
forms that can still be manipulating even if there is multiple tens of |
|||
thousands of elements in the select. It's made possible using JS libaries |
|||
Twitter Typeahead and Splitree's Tokenfield. |
|||
See docstring of massive_bootstrap_form for a detailed explaantion on how |
|||
to use this templatetag. |
|||
""" |
|||
|
|||
from django import template |
|||
from django.utils.safestring import mark_safe |
|||
from django.forms import TextInput |
|||
from django.forms.widgets import Select |
|||
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 : |
|||
'update_on' : { |
|||
'field_A' : [ 'id0', 'id1', ... ] , |
|||
'field_B' : ... , |
|||
... |
|||
} |
|||
|
|||
gen_select (optional) |
|||
A dict of boolean telling if the form should either generate |
|||
the normal select (set to true) and then use it to generate |
|||
the possible choices and then remove it or either (set to |
|||
false) generate the choices variable in this tag and do not |
|||
send any select. |
|||
Sending the select before can be usefull to permit the use |
|||
without any JS enabled but it will execute more code locally |
|||
for the client so the loading might be slower. |
|||
If not specified, this variable is set to true for each field |
|||
Example : |
|||
'gen_select' : { |
|||
'field_A': True , |
|||
'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>' |
|||
[, ... ] ] ] |
|||
} ], |
|||
[, 'gen_select': { |
|||
[ '<field1>': '<gen_select1>' |
|||
[, '<field2>': '<gen_select2>' |
|||
[, ... ] ] ] |
|||
} ] |
|||
} ] |
|||
[ <standard boostrap_form parameters> ] |
|||
%} |
|||
|
|||
**Example**: |
|||
|
|||
{% massive_bootstrap_form form 'ipv4' choices='[...]' %} |
|||
""" |
|||
|
|||
mbf_form = MBFForm(form, mbf_fields.split(','), *args, **kwargs) |
|||
return mbf_form.render() |
|||
|
|||
|
|||
|
|||
|
|||
class MBFForm(): |
|||
""" An object to hold all the information and useful methods needed to |
|||
create and render a massive django form into an actual HTML and JS |
|||
code able to handle it correctly. |
|||
Every field that is not listed is rendered as a normal bootstrap_field. |
|||
""" |
|||
|
|||
|
|||
def __init__(self, form, mbf_fields, *args, **kwargs): |
|||
# The django form object |
|||
self.form = form |
|||
# The fields on which to use JS |
|||
self.fields = mbf_fields |
|||
|
|||
# Other bootstrap_form arguments to render the fields |
|||
self.args = args |
|||
self.kwargs = kwargs |
|||
|
|||
# Fields to exclude form the form rendering |
|||
self.exclude = self.kwargs.get('exclude', '').split(',') |
|||
|
|||
# All the mbf parameters specified byt the user |
|||
param = kwargs.pop('mbf_param', {}) |
|||
self.choices = param.get('choices', {}) |
|||
self.engine = param.get('engine', {}) |
|||
self.match_func = param.get('match_func', {}) |
|||
self.update_on = param.get('update_on', {}) |
|||
self.gen_select = param.get('gen_select', {}) |
|||
self.hidden_fields = [h.name for h in self.form.hidden_fields()] |
|||
|
|||
# HTML code to insert inside a template |
|||
self.html = "" |
|||
|
|||
|
|||
def render(self): |
|||
""" HTML code for the fully rendered form with all the necessary form |
|||
""" |
|||
for name, field in self.form.fields.items(): |
|||
if not name in self.exclude: |
|||
|
|||
if name in self.fields and not name in self.hidden_fields: |
|||
mbf_field = MBFField( |
|||
name, |
|||
field, |
|||
field.get_bound_field(self.form, name), |
|||
self.choices.get(name, None), |
|||
self.engine.get(name, None), |
|||
self.match_func.get(name, None), |
|||
self.update_on.get(name, None), |
|||
self.gen_select.get(name, True), |
|||
*self.args, |
|||
**self.kwargs |
|||
) |
|||
self.html += mbf_field.render() |
|||
|
|||
else: |
|||
self.html += render_field( |
|||
field.get_bound_field(self.form, name), |
|||
*self.args, |
|||
**self.kwargs |
|||
) |
|||
|
|||
return mark_safe(self.html) |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
class MBFField(): |
|||
""" An object to hold all the information and useful methods needed to |
|||
create and render a massive django form field into an actual HTML and JS |
|||
code able to handle it correctly. |
|||
Twitter Typeahead is used for the display and the matching of queries and |
|||
in case of a MultipleSelect, Sliptree's Tokenfield is also used to manage |
|||
multiple values. |
|||
A div with only non visible elements is created after the div containing |
|||
the displayed input. It's used to store the actual data that will be sent |
|||
to the server """ |
|||
|
|||
|
|||
def __init__(self, name_, field_, bound_, choices_, engine_, match_func_, |
|||
update_on_, gen_select_, *args_, **kwargs_): |
|||
|
|||
# Verify this field is a Select (or MultipleSelect) (only supported) |
|||
if not isinstance(field_.widget, Select): |
|||
raise ValueError( |
|||
('Field named {f_name} is not a Select and' |
|||
'can\'t be rendered with massive_bootstrap_form.' |
|||
).format( |
|||
f_name=name_ |
|||
) |
|||
) |
|||
|
|||
# Name of the field |
|||
self.name = name_ |
|||
# Django field object |
|||
self.field = field_ |
|||
# Bound Django field associated with field |
|||
self.bound = bound_ |
|||
|
|||
# Id for the main visible input |
|||
self.input_id = self.bound.auto_id |
|||
# Id for a hidden input used to store the value |
|||
self.hidden_id = self.input_id + '_hidden' |
|||
# Id for another div containing hidden inputs and script |
|||
self.div2_id = self.input_id + '_div' |
|||
|
|||
# Should the standard select should be generated |
|||
self.gen_select = gen_select_ |
|||
# Is it select with multiple values possible (use of tokenfield) |
|||
self.multiple = self.field.widget.allow_multiple_selected |
|||
# JS for the choices variable (user specified or default) |
|||
self.choices = choices_ or self.default_choices() |
|||
# JS for the engine variable (typeahead) (user specified or default) |
|||
self.engine = engine_ or self.default_engine() |
|||
# JS for the matching function (typeahead) (user specified or default) |
|||
self.match_func = match_func_ or self.default_match_func() |
|||
# JS for the datasets variable (typeahead) (user specified or default) |
|||
self.datasets = self.default_datasets() |
|||
# Ids of other fields to bind a reset/reload with when changed |
|||
self.update_on = update_on_ or [] |
|||
|
|||
# Whole HTML code to insert in the template |
|||
self.html = "" |
|||
# JS code in the script tag |
|||
self.js_script = "" |
|||
# Input tag to display instead of select |
|||
self.replace_input = None |
|||
|
|||
# Other bootstrap_form arguments to render the fields |
|||
self.args = args_ |
|||
self.kwargs = kwargs_ |
|||
|
|||
|
|||
def default_choices(self): |
|||
""" JS code of the variable choices_<fieldname> """ |
|||
|
|||
if self.gen_select: |
|||
return ( |
|||
'function plop(o) {{' |
|||
'var c = [];' |
|||
'for( let i=0 ; i<o.length ; i++) {{' |
|||
' c.push( {{ key: o[i].value, value: o[i].text }} );' |
|||
'}}' |
|||
'return c;' |
|||
'}} ($("#{select_id}")[0].options)' |
|||
).format( |
|||
select_id=self.input_id |
|||
) |
|||
|
|||
else: |
|||
return '[{objects}]'.format( |
|||
objects=','.join( |
|||
['{{key:{k},value:"{v}"}}'.format( |
|||
k=choice[0] if choice[0] != '' else '""', |
|||
v=choice[1] |
|||
) for choice in self.field.choices] |
|||
) |
|||
) |
|||
|
|||
|
|||
def default_engine(self): |
|||
""" Default JS code of the variable engine_<field_name> """ |
|||
return ( |
|||
'new Bloodhound({{' |
|||
' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),' |
|||
' queryTokenizer: Bloodhound.tokenizers.whitespace,' |
|||
' local: choices_{name},' |
|||
' identify: function(obj) {{ return obj.key; }}' |
|||
'}})' |
|||
).format( |
|||
name=self.name |
|||
) |
|||
|
|||
|
|||
def default_datasets(self): |
|||
""" Default JS script of the datasets to use with typeahead """ |
|||
return ( |
|||
'{{' |
|||
' hint: true,' |
|||
' highlight: true,' |
|||
' minLength: 0' |
|||
'}},' |
|||
'{{' |
|||
' display: "value",' |
|||
' name: "{name}",' |
|||
' source: {match_func}' |
|||
'}}' |
|||
).format( |
|||
name=self.name, |
|||
match_func=self.match_func |
|||
) |
|||
|
|||
|
|||
def default_match_func(self): |
|||
""" Default JS code of the matching function to use with typeahed """ |
|||
return ( |
|||
'function ( q, sync ) {{' |
|||
' if ( q === "" ) {{' |
|||
' var first = choices_{name}.slice( 0, 5 ).map(' |
|||
' function ( obj ) {{ return obj.key; }}' |
|||
' );' |
|||
' sync( engine_{name}.get( first ) );' |
|||
' }} else {{' |
|||
' engine_{name}.search( q, sync );' |
|||
' }}' |
|||
'}}' |
|||
).format( |
|||
name=self.name |
|||
) |
|||
|
|||
|
|||
def render(self): |
|||
""" HTML code for the fully rendered field """ |
|||
self.gen_displayed_div() |
|||
self.gen_hidden_div() |
|||
return mark_safe(self.html) |
|||
|
|||
|
|||
def gen_displayed_div(self): |
|||
""" Generate HTML code for the div that contains displayed tags """ |
|||
if self.gen_select: |
|||
self.html += render_field( |
|||
self.bound, |
|||
*self.args, |
|||
**self.kwargs |
|||
) |
|||
|
|||
self.field.widget = TextInput( |
|||
attrs={ |
|||
'name': 'mbf_'+self.name, |
|||
'placeholder': self.field.empty_label |
|||
} |
|||
) |
|||
self.replace_input = render_field( |
|||
self.bound, |
|||
*self.args, |
|||
**self.kwargs |
|||
) |
|||
|
|||
if not self.gen_select: |
|||
self.html += self.replace_input |
|||
|
|||
|
|||
def gen_hidden_div(self): |
|||
""" Generate HTML code for the div that contains hidden tags """ |
|||
self.gen_full_js() |
|||
|
|||
content = self.js_script |
|||
if not self.multiple and not self.gen_select: |
|||
content += self.hidden_input() |
|||
|
|||
self.html += render_tag( |
|||
'div', |
|||
content=content, |
|||
attrs={'id': self.div2_id} |
|||
) |
|||
|
|||
|
|||
def hidden_input(self): |
|||
""" HTML for the hidden input element """ |
|||
return render_tag( |
|||
'input', |
|||
attrs={ |
|||
'id': self.hidden_id, |
|||
'name': self.bound.html_name, |
|||
'type': 'hidden', |
|||
'value': self.bound.value() or "" |
|||
} |
|||
) |
|||
|
|||
|
|||
def gen_full_js(self): |
|||
""" Generate the full script tag containing the JS code """ |
|||
self.create_js() |
|||
self.fill_js() |
|||
self.get_script() |
|||
|
|||
|
|||
def create_js(self): |
|||
""" Generate a template for the whole script to use depending on |
|||
gen_select and multiple """ |
|||
if self.gen_select: |
|||
if self.multiple: |
|||
self.js_script = ( |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
' var choices_{f_name} = {choices};' |
|||
' {del_select}' |
|||
' 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", {tok_create} );' |
|||
' $( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );' |
|||
' $( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );' |
|||
' {tok_updates}' |
|||
' setup_{f_name}();' |
|||
' {tok_init_input}' |
|||
'}} );' |
|||
) |
|||
else: |
|||
self.js_script = ( |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
' var choices_{f_name} = {choices};' |
|||
' {del_select}' |
|||
' {gen_hidden}' |
|||
' 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", {typ_select} );' |
|||
' $( "#{input_id}" ).bind( "typeahead:change", {typ_change} );' |
|||
' {typ_updates}' |
|||
' setup_{f_name}();' |
|||
' {typ_init_input}' |
|||
'}} );' |
|||
) |
|||
else: |
|||
if self.multiple: |
|||
self.js_script = ( |
|||
'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", {tok_create} );' |
|||
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );' |
|||
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );' |
|||
'{tok_updates}' |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
' setup_{f_name}();' |
|||
' {tok_init_input}' |
|||
'}} );' |
|||
) |
|||
else: |
|||
self.js_script = ( |
|||
'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", {typ_select} );' |
|||
'$( "#{input_id}" ).bind( "typeahead:change", {typ_change} );' |
|||
'{typ_updates}' |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
' setup_{f_name}();' |
|||
' {typ_init_input}' |
|||
'}} );' |
|||
) |
|||
|
|||
|
|||
def fill_js(self): |
|||
""" Fill the template with the correct values """ |
|||
self.js_script = self.js_script.format( |
|||
f_name=self.name, |
|||
choices=self.choices, |
|||
del_select=self.del_select(), |
|||
gen_hidden=self.gen_hidden(), |
|||
engine=self.engine, |
|||
input_id=self.input_id, |
|||
datasets=self.datasets, |
|||
typ_select=self.typeahead_select(), |
|||
typ_change=self.typeahead_change(), |
|||
tok_create=self.tokenfield_create(), |
|||
tok_edit=self.tokenfield_edit(), |
|||
tok_remove=self.tokenfield_remove(), |
|||
typ_updates=self.typeahead_updates(), |
|||
tok_updates=self.tokenfield_updates(), |
|||
tok_init_input=self.tokenfield_init_input(), |
|||
typ_init_input=self.typeahead_init_input() |
|||
) |
|||
|
|||
|
|||
def get_script(self): |
|||
""" Insert the JS code inside a script tag """ |
|||
self.js_script = render_tag('script', content=mark_safe(self.js_script)) |
|||
|
|||
|
|||
def del_select(self): |
|||
""" JS code to delete the select if it has been generated and replace |
|||
it with an input. """ |
|||
return ( |
|||
'var p = $("#{select_id}").parent()[0];' |
|||
'var new_input = `{replace_input}`;' |
|||
'p.innerHTML = new_input;' |
|||
).format( |
|||
select_id=self.input_id, |
|||
replace_input=self.replace_input |
|||
) |
|||
|
|||
|
|||
def gen_hidden(self): |
|||
""" JS code to add a hidden tag to store the value. """ |
|||
return ( |
|||
'var d = $("#{div2_id}")[0];' |
|||
'var i = document.createElement("input");' |
|||
'i.id = "{hidden_id}";' |
|||
'i.name = "{html_name}";' |
|||
'i.value = "";' |
|||
'i.type = "hidden";' |
|||
'd.appendChild(i);' |
|||
).format( |
|||
div2_id=self.div2_id, |
|||
hidden_id=self.hidden_id, |
|||
html_name=self.bound.html_name |
|||
) |
|||
|
|||
|
|||
def typeahead_init_input(self): |
|||
""" JS code to init the fields values """ |
|||
init_key = self.bound.value() or '""' |
|||
return ( |
|||
'$( "#{input_id}" ).typeahead("val", {init_val});' |
|||
'$( "#{hidden_id}" ).val( {init_key} );' |
|||
).format( |
|||
input_id=self.input_id, |
|||
init_val='""' if init_key == '""' else |
|||
'engine_{name}.get( {init_key} )[0].value'.format( |
|||
name=self.name, |
|||
init_key=init_key |
|||
), |
|||
init_key=init_key, |
|||
hidden_id=self.hidden_id |
|||
) |
|||
|
|||
|
|||
def typeahead_reset_input(self): |
|||
""" JS code to reset the fields values """ |
|||
return ( |
|||
'$( "#{input_id}" ).typeahead("val", "");' |
|||
'$( "#{hidden_id}" ).val( "" );' |
|||
).format( |
|||
input_id=self.input_id, |
|||
hidden_id=self.hidden_id |
|||
) |
|||
|
|||
|
|||
def typeahead_select(self): |
|||
""" JS code to create 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=self.hidden_id |
|||
) |
|||
|
|||
|
|||
def typeahead_change(self): |
|||
""" JS code of 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=self.input_id, |
|||
hidden_id=self.hidden_id |
|||
) |
|||
|
|||
|
|||
def typeahead_updates(self): |
|||
""" JS code for binding external fields changes with a reset """ |
|||
reset_input = self.typeahead_reset_input() |
|||
updates = [ |
|||
( |
|||
'$( "#{u_id}" ).change( function() {{' |
|||
' setup_{name}();' |
|||
' {reset_input}' |
|||
'}} );' |
|||
).format( |
|||
u_id=u_id, |
|||
name=self.name, |
|||
reset_input=reset_input |
|||
) for u_id in self.update_on] |
|||
return ''.join(updates) |
|||
|
|||
|
|||
def tokenfield_init_input(self): |
|||
""" JS code to init the fields values """ |
|||
init_key = self.bound.value() or '""' |
|||
return ( |
|||
'$( "#{input_id}" ).tokenfield("setTokens", {init_val});' |
|||
).format( |
|||
input_id=self.input_id, |
|||
init_val='""' if init_key == '""' else ( |
|||
'engine_{name}.get( {init_key} ).map(' |
|||
' function(o) {{ return o.value; }}' |
|||
')').format( |
|||
name=self.name, |
|||
init_key=init_key |
|||
) |
|||
) |
|||
|
|||
|
|||
def tokenfield_reset_input(self): |
|||
""" JS code to reset the fields values """ |
|||
return ( |
|||
'$( "#{input_id}" ).tokenfield("setTokens", "");' |
|||
).format( |
|||
input_id=self.input_id |
|||
) |
|||
|
|||
|
|||
def tokenfield_create(self): |
|||
""" JS code 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_{name}.length &&' |
|||
' choices_{name}[i].value !== data ) {{' |
|||
' i++;' |
|||
' }}' |
|||
' if ( i === choices_{name}.length ) {{ return false; }}' |
|||
' k = choices_{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 = "{html_name}";' |
|||
' $( "#{div2_id}" ).append(new_input);' |
|||
'}}' |
|||
).format( |
|||
name=self.name, |
|||
hidden_id=self.hidden_id, |
|||
html_name=self.bound.html_name, |
|||
div2_id=self.div2_id |
|||
) |
|||
|
|||
|
|||
def tokenfield_edit(self): |
|||
""" JS code 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_{name}.length &&' |
|||
' choices_{name}[i].value !== data ) {{' |
|||
' i++;' |
|||
' }}' |
|||
' if ( i === choices_{name}.length ) {{ return true; }}' |
|||
' k = choices_{name}[i].key;' |
|||
' }}' |
|||
' var old_input = document.getElementById(' |
|||
' "{hidden_id}_"+k.toString()' |
|||
' );' |
|||
' old_input.parentNode.removeChild(old_input);' |
|||
'}}' |
|||
).format( |
|||
name=self.name, |
|||
hidden_id=self.hidden_id |
|||
) |
|||
|
|||
|
|||
def tokenfield_remove(self): |
|||
""" JS code 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_{name}.length &&' |
|||
' choices_{name}[i].value !== data ) {{' |
|||
' i++;' |
|||
' }}' |
|||
' if ( i === choices_{name}.length ) {{ return true; }}' |
|||
' k = choices_{name}[i].key;' |
|||
' }}' |
|||
' var old_input = document.getElementById(' |
|||
' "{hidden_id}_"+k.toString()' |
|||
' );' |
|||
' old_input.parentNode.removeChild(old_input);' |
|||
'}}' |
|||
).format( |
|||
name=self.name, |
|||
hidden_id=self.hidden_id |
|||
) |
|||
|
|||
|
|||
def tokenfield_updates(self): |
|||
""" JS code for binding external fields changes with a reset """ |
|||
reset_input = self.tokenfield_reset_input() |
|||
updates = [ |
|||
( |
|||
'$( "#{u_id}" ).change( function() {{' |
|||
' setup_{name}();' |
|||
' {reset_input}' |
|||
'}} );' |
|||
).format( |
|||
u_id=u_id, |
|||
name=self.name, |
|||
reset_input=reset_input |
|||
) for u_id in self.update_on] |
|||
return ''.join(updates) |
|||
@ -0,0 +1,105 @@ |
|||
# -*- 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. |
|||
|
|||
""" |
|||
Templatetag used to write a URL (specified or current one) and adding |
|||
or inserting specific parameters into the query part without deleting |
|||
the other parameters. |
|||
""" |
|||
|
|||
from django import template |
|||
|
|||
register = template.Library() |
|||
|
|||
|
|||
@register.simple_tag |
|||
def url_insert_param(url="", **kwargs): |
|||
""" |
|||
Return the URL with some specific parameters inserted into the query |
|||
part. If a URL has already some parameters, those requested will be |
|||
modified if already exisiting or will be added and the other parameters |
|||
will stay unmodified. If parameters with the same name are already in the |
|||
URL and a value is specified for this parameter, it will replace all |
|||
existing parameters. |
|||
|
|||
**Tag name**:: |
|||
|
|||
url_insert_param |
|||
|
|||
**Parameters**: |
|||
|
|||
url (optional) |
|||
The URL to use as a base. The parameters will be added to this URL. |
|||
If not specified, it will only return the query part of the URL |
|||
("?a=foo&b=bar" for example). |
|||
Example : "https://example.com/bar?foo=0&thing=abc" |
|||
|
|||
other arguments |
|||
Any other key-value argument will be used. The key is considered as |
|||
the name of the parameter to insert/modify and the value is the one |
|||
used. |
|||
Example : q="foo" search="bar" name="johnDoe" |
|||
will return as ?<existing_param>&q=foo&search=bar&name=johnDoe |
|||
|
|||
**Usage**:: |
|||
|
|||
{% url_insert_param [URL] [param1=val1 [param2=val2 [...]]] %} |
|||
|
|||
**Example**:: |
|||
|
|||
{% url_insert_param a=0 b="bar" %} |
|||
return "?a=0&b=bar" |
|||
|
|||
{% url_insert_param "url.net/foo.html" a=0 b="bar" %} |
|||
return "url.net/foo.html?a=0&b=bar" |
|||
|
|||
{% url_insert_param "url.net/foo.html?c=keep" a=0 b="bar" %} |
|||
return "url.net/foo.html?c=keep&a=0&b=bar" |
|||
|
|||
{% url_insert_param "url.net/foo.html?a=del" a=0 b="bar" %} |
|||
return "url.net/foo.html?a=0&b=bar" |
|||
|
|||
{% url_insert_param "url.net/foo.html?a=del&c=keep" a=0 b="bar" %} |
|||
return "url.net/foo.hmtl?a=0&c=keep&b=bar" |
|||
""" |
|||
|
|||
# Get existing parameters in the url |
|||
params = {} |
|||
if '?' in url: |
|||
url, parameters = url.split('?', maxsplit=1) |
|||
for parameter in parameters.split('&'): |
|||
p_name, p_value = parameter.split('=', maxsplit=1) |
|||
if p_name not in params: |
|||
params[p_name] = [] |
|||
params[p_name].append(p_value) |
|||
|
|||
# Add the request parameters to the list of parameters |
|||
for key, value in kwargs.items(): |
|||
params[key] = [value] |
|||
|
|||
# Write the url |
|||
url += '?' |
|||
for param, value_list in params.items(): |
|||
for value in value_list: |
|||
url += str(param) + '=' + str(value) + '&' |
|||
|
|||
# Remove the last '&' (or '?' if no parameters) |
|||
return url[:-1] |
|||
@ -0,0 +1,264 @@ |
|||
# -*- 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 Gabriel Détraz |
|||
# Copyright © 2017 Goulven Kermarec |
|||
# Copyright © 2017 Augustin Lemesle |
|||
# |
|||
# 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. |
|||
|
|||
# -*- coding: utf-8 -*- |
|||
# David Sinquin, Gabriel Détraz, Goulven Kermarec |
|||
""" |
|||
Regroupe les fonctions transversales utiles |
|||
|
|||
Fonction : |
|||
- récupérer tous les utilisateurs actifs |
|||
- récupérer toutes les machines |
|||
- récupérer tous les bans |
|||
etc |
|||
""" |
|||
|
|||
|
|||
from __future__ import unicode_literals |
|||
|
|||
|
|||
from django.utils import timezone |
|||
from django.db.models import Q |
|||
|
|||
from cotisations.models import Cotisation, Facture, Paiement, Vente |
|||
from machines.models import Domain, Interface, Machine |
|||
from users.models import Adherent, User, Ban, Whitelist |
|||
from preferences.models import Service |
|||
|
|||
DT_NOW = timezone.now() |
|||
|
|||
|
|||
def all_adherent(search_time=DT_NOW): |
|||
""" Fonction renvoyant tous les users adherents. Optimisee pour n'est |
|||
qu'une seule requete sql |
|||
Inspecte les factures de l'user et ses cotisation, regarde si elles |
|||
sont posterieur à now (end_time)""" |
|||
return User.objects.filter( |
|||
facture__in=Facture.objects.filter( |
|||
vente__in=Vente.objects.filter( |
|||
Q(type_cotisation='All') | Q(type_cotisation='Adhesion'), |
|||
cotisation__in=Cotisation.objects.filter( |
|||
vente__in=Vente.objects.filter( |
|||
facture__in=Facture.objects.all().exclude(valid=False) |
|||
) |
|||
).filter(date_end__gt=search_time) |
|||
) |
|||
) |
|||
).distinct() |
|||
|
|||
|
|||
def all_baned(search_time=DT_NOW): |
|||
""" Fonction renvoyant tous les users bannis """ |
|||
return User.objects.filter( |
|||
ban__in=Ban.objects.filter( |
|||
date_end__gt=search_time |
|||
) |
|||
).distinct() |
|||
|
|||
|
|||
def all_whitelisted(search_time=DT_NOW): |
|||
""" Fonction renvoyant tous les users whitelistes """ |
|||
return User.objects.filter( |
|||
whitelist__in=Whitelist.objects.filter( |
|||
date_end__gt=search_time |
|||
) |
|||
).distinct() |
|||
|
|||
|
|||
def all_has_access(search_time=DT_NOW): |
|||
""" Renvoie tous les users beneficiant d'une connexion |
|||
: user adherent ou whiteliste et non banni """ |
|||
return User.objects.filter( |
|||
Q(state=User.STATE_ACTIVE) & |
|||
~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) & |
|||
(Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) | |
|||
Q(facture__in=Facture.objects.filter( |
|||
vente__in=Vente.objects.filter( |
|||
cotisation__in=Cotisation.objects.filter( |
|||
Q(type_cotisation='All') | Q(type_cotisation='Connexion'), |
|||
vente__in=Vente.objects.filter( |
|||
facture__in=Facture.objects.all() |
|||
.exclude(valid=False) |
|||
) |
|||
).filter(date_end__gt=search_time) |
|||
) |
|||
))) |
|||
).distinct() |
|||
|
|||
|
|||
def filter_active_interfaces(interface_set): |
|||
"""Filtre les machines autorisées à sortir sur internet dans une requête""" |
|||
return interface_set.filter( |
|||
machine__in=Machine.objects.filter( |
|||
user__in=all_has_access() |
|||
).filter(active=True) |
|||
).select_related('domain').select_related('machine')\ |
|||
.select_related('type').select_related('ipv4')\ |
|||
.select_related('domain__extension').select_related('ipv4__ip_type')\ |
|||
.distinct() |
|||
|
|||
|
|||
def all_active_interfaces(): |
|||
"""Renvoie l'ensemble des machines autorisées à sortir sur internet """ |
|||
return filter_active_interfaces(Interface.objects) |
|||
|
|||
|
|||
def all_active_assigned_interfaces(): |
|||
""" Renvoie l'ensemble des machines qui ont une ipv4 assignées et |
|||
disposant de l'accès internet""" |
|||
return all_active_interfaces().filter(ipv4__isnull=False) |
|||
|
|||
|
|||
def all_active_interfaces_count(): |
|||
""" Version light seulement pour compter""" |
|||
return Interface.objects.filter( |
|||
machine__in=Machine.objects.filter( |
|||
user__in=all_has_access() |
|||
).filter(active=True) |
|||
) |
|||
|
|||
|
|||
def all_active_assigned_interfaces_count(): |
|||
""" Version light seulement pour compter""" |
|||
return all_active_interfaces_count().filter(ipv4__isnull=False) |
|||
|
|||
class SortTable: |
|||
""" Class gathering uselful stuff to sort the colums of a table, according |
|||
to the column and order requested. It's used with a dict of possible |
|||
values and associated model_fields """ |
|||
|
|||
# All the possible possible values |
|||
# The naming convention is based on the URL or the views function |
|||
# The syntax to describe the sort to apply is a dict where the keys are |
|||
# the url value and the values are a list of model field name to use to |
|||
# order the request. They are applied in the order they are given. |
|||
# A 'default' might be provided to specify what to do if the requested col |
|||
# doesn't match any keys. |
|||
USERS_INDEX = { |
|||
'user_name': ['name'], |
|||
'user_surname': ['surname'], |
|||
'user_pseudo': ['pseudo'], |
|||
'user_room': ['room'], |
|||
'default': ['state', 'pseudo'] |
|||
} |
|||
USERS_INDEX_BAN = { |
|||
'ban_user': ['user__pseudo'], |
|||
'ban_start': ['date_start'], |
|||
'ban_end': ['date_end'], |
|||
'default': ['-date_end'] |
|||
} |
|||
USERS_INDEX_WHITE = { |
|||
'white_user': ['user__pseudo'], |
|||
'white_start': ['date_start'], |
|||
'white_end': ['date_end'], |
|||
'default': ['-date_end'] |
|||
} |
|||
MACHINES_INDEX = { |
|||
'machine_name': ['name'], |
|||
'default': ['pk'] |
|||
} |
|||
COTISATIONS_INDEX = { |
|||
'cotis_user': ['user__pseudo'], |
|||
'cotis_paiement': ['paiement__moyen'], |
|||
'cotis_date': ['date'], |
|||
'cotis_id': ['id'], |
|||
'default': ['-date'] |
|||
} |
|||
COTISATIONS_CONTROL = { |
|||
'control_name': ['user__adherent__name'], |
|||
'control_surname': ['user__surname'], |
|||
'control_paiement': ['paiement'], |
|||
'control_date': ['date'], |
|||
'control_valid': ['valid'], |
|||
'control_control': ['control'], |
|||
'control_id': ['id'], |
|||
'control_user-id': ['user__id'], |
|||
'default': ['-date'] |
|||
} |
|||
TOPOLOGIE_INDEX = { |
|||
'switch_dns': ['switch_interface__domain__name'], |
|||
'switch_ip': ['switch_interface__ipv4__ipv4'], |
|||
'switch_loc': ['location'], |
|||
'switch_ports': ['number'], |
|||
'switch_stack': ['stack__name'], |
|||
'default': ['location', 'stack', 'stack_member_id'] |
|||
} |
|||
TOPOLOGIE_INDEX_PORT = { |
|||
'port_port': ['port'], |
|||
'port_room': ['room__name'], |
|||
'port_interface': ['machine_interface__domain__name'], |
|||
'port_related': ['related__switch__name'], |
|||
'port_radius': ['radius'], |
|||
'port_vlan': ['vlan_force__name'], |
|||
'default': ['port'] |
|||
} |
|||
TOPOLOGIE_INDEX_ROOM = { |
|||
'room_name': ['name'], |
|||
'default': ['name'] |
|||
} |
|||
TOPOLOGIE_INDEX_STACK = { |
|||
'stack_name': ['name'], |
|||
'stack_id': ['stack_id'], |
|||
'default': ['stack_id'], |
|||
} |
|||
TOPOLOGIE_INDEX_MODEL_SWITCH = { |
|||
'model_switch_name': ['reference'], |
|||
'model_switch__contructor' : ['constructor__name'], |
|||
'default': ['reference'], |
|||
} |
|||
TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH = { |
|||
'room_name': ['name'], |
|||
'default': ['name'], |
|||
} |
|||
LOGS_INDEX = { |
|||
'sum_date': ['revision__date_created'], |
|||
'default': ['-revision__date_created'], |
|||
} |
|||
LOGS_STATS_LOGS = { |
|||
'logs_author': ['user__name'], |
|||
'logs_date': ['date_created'], |
|||
'default': ['-date_created'] |
|||
} |
|||
|
|||
@staticmethod |
|||
def sort(request, col, order, values): |
|||
""" Check if the given values are possible and add .order_by() and |
|||
a .reverse() as specified according to those values """ |
|||
fields = values.get(col, None) |
|||
if not fields: |
|||
fields = values.get('default', []) |
|||
request = request.order_by(*fields) |
|||
if values.get(col, None) and order == 'desc': |
|||
return request.reverse() |
|||
else: |
|||
return request |
|||
|
|||
|
|||
def remove_user_room(room): |
|||
""" Déménage de force l'ancien locataire de la chambre """ |
|||
try: |
|||
user = Adherent.objects.get(room=room) |
|||
except Adherent.DoesNotExist: |
|||
return |
|||
user.room = None |
|||
user.save() |
|||
@ -1,62 +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 Gabriel Détraz |
|||
# Copyright © 2017 Goulven Kermarec |
|||
# Copyright © 2017 Augustin Lemesle |
|||
# |
|||
# 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 __future__ import unicode_literals |
|||
|
|||
from django.db import models |
|||
from django import forms |
|||
from django.forms import Form |
|||
from django.forms import ModelForm |
|||
|
|||
CHOICES = ( |
|||
('0', 'Actifs'), |
|||
('1', 'Désactivés'), |
|||
('2', 'Archivés'), |
|||
) |
|||
|
|||
CHOICES2 = ( |
|||
(1, 'Active'), |
|||
("", 'Désactivée'), |
|||
) |
|||
|
|||
CHOICES3 = ( |
|||
('0', 'Utilisateurs'), |
|||
('1', 'Machines'), |
|||
('2', 'Factures'), |
|||
('3', 'Bannissements'), |
|||
('4', 'Accès à titre gracieux'), |
|||
('6', 'Switchs'), |
|||
('5', 'Ports'), |
|||
) |
|||
|
|||
|
|||
class SearchForm(Form): |
|||
search_field = forms.CharField(label = 'Search', max_length = 100) |
|||
|
|||
class SearchFormPlus(Form): |
|||
search_field = forms.CharField(label = 'Search', max_length = 100, required=False) |
|||
filtre = forms.MultipleChoiceField(label="Filtre utilisateurs", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES) |
|||
connexion = forms.MultipleChoiceField(label="Filtre connexion", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES2) |
|||
affichage = forms.MultipleChoiceField(label="Filtre affichage", required=False, widget =forms.CheckboxSelectMultiple,choices=CHOICES3) |
|||
date_deb = forms.DateField(required=False, label="Date de début", help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y']) |
|||
date_fin = forms.DateField(required=False, help_text='DD/MM/YYYY', input_formats=['%d/%m/%Y'], label="Date de fin") |
|||
@ -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; |
|||
} |
|||
@ -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. |
|||
File diff suppressed because it is too large
@ -0,0 +1,19 @@ |
|||
Copyright (C) 2011-2017 by Yehuda Katz |
|||
|
|||
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. |
|||
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2017 Snaptortoise |
|||
|
|||
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. |
|||
@ -0,0 +1,139 @@ |
|||
/* |
|||
* Konami-JS ~ |
|||
* :: Now with support for touch events and multiple instances for |
|||
* :: those situations that call for multiple easter eggs! |
|||
* Code: https://github.com/snaptortoise/konami-js
|
|||
* Examples: http://www.snaptortoise.com/konami-js
|
|||
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com) |
|||
* Version: 1.5.1 (9/4/2017) |
|||
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
|
|||
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1+ and Android |
|||
*/ |
|||
|
|||
var Konami = function (callback) { |
|||
var konami = { |
|||
addEvent: function (obj, type, fn, ref_obj) { |
|||
if (obj.addEventListener) |
|||
obj.addEventListener(type, fn, false); |
|||
else if (obj.attachEvent) { |
|||
// IE
|
|||
obj["e" + type + fn] = fn; |
|||
obj[type + fn] = function () { |
|||
obj["e" + type + fn](window.event, ref_obj); |
|||
} |
|||
obj.attachEvent("on" + type, obj[type + fn]); |
|||
} |
|||
}, |
|||
removeEvent: function (obj, eventName, eventCallback) { |
|||
if (obj.removeEventListener) { |
|||
obj.removeEventListener(eventName, eventCallback); |
|||
} else if (obj.attachEvent) { |
|||
obj.detachEvent(eventName); |
|||
} |
|||
}, |
|||
input: "", |
|||
pattern: "38384040373937396665", |
|||
keydownHandler: function (e, ref_obj) { |
|||
if (ref_obj) { |
|||
konami = ref_obj; |
|||
} // IE
|
|||
konami.input += e ? e.keyCode : event.keyCode; |
|||
if (konami.input.length > konami.pattern.length) { |
|||
konami.input = konami.input.substr((konami.input.length - konami.pattern.length)); |
|||
} |
|||
if (konami.input === konami.pattern) { |
|||
konami.code(this._currentlink); |
|||
konami.input = ''; |
|||
e.preventDefault(); |
|||
return false; |
|||
} |
|||
}, |
|||
load: function (link) { |
|||
this.addEvent(document, "keydown", this.keydownHandler, this); |
|||
this.iphone.load(link); |
|||
}, |
|||
unload: function () { |
|||
this.removeEvent(document, 'keydown', this.keydownHandler); |
|||
this.iphone.unload(); |
|||
}, |
|||
code: function (link) { |
|||
window.location = link |
|||
}, |
|||
iphone: { |
|||
start_x: 0, |
|||
start_y: 0, |
|||
stop_x: 0, |
|||
stop_y: 0, |
|||
tap: false, |
|||
capture: false, |
|||
orig_keys: "", |
|||
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"], |
|||
input: [], |
|||
code: function (link) { |
|||
konami.code(link); |
|||
}, |
|||
touchmoveHandler: function (e) { |
|||
if (e.touches.length === 1 && konami.iphone.capture === true) { |
|||
var touch = e.touches[0]; |
|||
konami.iphone.stop_x = touch.pageX; |
|||
konami.iphone.stop_y = touch.pageY; |
|||
konami.iphone.tap = false; |
|||
konami.iphone.capture = false; |
|||
konami.iphone.check_direction(); |
|||
} |
|||
}, |
|||
toucheendHandler: function () { |
|||
if (konami.iphone.tap === true) { |
|||
konami.iphone.check_direction(this._currentLink); |
|||
} |
|||
}, |
|||
touchstartHandler: function (e) { |
|||
konami.iphone.start_x = e.changedTouches[0].pageX; |
|||
konami.iphone.start_y = e.changedTouches[0].pageY; |
|||
konami.iphone.tap = true; |
|||
konami.iphone.capture = true; |
|||
}, |
|||
load: function (link) { |
|||
this.orig_keys = this.keys; |
|||
konami.addEvent(document, "touchmove", this.touchmoveHandler); |
|||
konami.addEvent(document, "touchend", this.toucheendHandler, false); |
|||
konami.addEvent(document, "touchstart", this.touchstartHandler); |
|||
}, |
|||
unload: function () { |
|||
konami.removeEvent(document, 'touchmove', this.touchmoveHandler); |
|||
konami.removeEvent(document, 'touchend', this.toucheendHandler); |
|||
konami.removeEvent(document, 'touchstart', this.touchstartHandler); |
|||
}, |
|||
check_direction: function () { |
|||
x_magnitude = Math.abs(this.start_x - this.stop_x); |
|||
y_magnitude = Math.abs(this.start_y - this.stop_y); |
|||
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT"; |
|||
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP"; |
|||
result = (x_magnitude > y_magnitude) ? x : y; |
|||
result = (this.tap === true) ? "TAP" : result; |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
|
|||
typeof callback === "string" && konami.load(callback); |
|||
if (typeof callback === "function") { |
|||
konami.code = callback; |
|||
konami.load(); |
|||
} |
|||
|
|||
return konami; |
|||
}; |
|||
|
|||
|
|||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { |
|||
module.exports = Konami; |
|||
} else { |
|||
if (typeof define === 'function' && define.amd) { |
|||
define([], function() { |
|||
return Konami; |
|||
}); |
|||
} else { |
|||
window.Konami = Konami; |
|||
} |
|||
} |
|||
@ -0,0 +1,316 @@ |
|||
// 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.
|
|||
|
|||
// General options
|
|||
//=====================================
|
|||
// Times the canvas is refreshed a second
|
|||
var FPS = 30; |
|||
// Determine the length of the trail (0=instant disappear, maximum=window.innerHeight=no disappear)
|
|||
var TRAIL_TIME = 5; |
|||
// The color of the characters
|
|||
var RAIN_COLOR = "#00F"; |
|||
// The characters displayed
|
|||
var CHARACTERS = "田由甲申甴电甶男甸甹町画甼甽甾甿畀畁畂畃畄畅畆畇畈畉畊畋界畍畎畏畐畑".split(""); |
|||
// The font size used to display the characters
|
|||
var FONT_SIZE = 10; |
|||
// The maximum number of characters displayed by column
|
|||
var MAX_CHAR = 7; |
|||
|
|||
var Sapphire = function () { |
|||
var sapphire = { |
|||
triggerHandle: undefined, |
|||
activated: false, |
|||
runOnce: false, |
|||
|
|||
getClass: function(elt, main, name) { elt.obj = main.getElementsByClassName(name); }, |
|||
getTag: function(elt, main, name) { elt.obj = main.getElementsByTagName(name); }, |
|||
|
|||
getProp: function(elt) { |
|||
for (var i=0 ; i<elt.obj.length ; i++) { |
|||
for (var p in elt.prop) { |
|||
if ( p === "color" ) { elt.prop[p][i] = elt.obj[i].style.color; } |
|||
else if ( p === "bgColor" ) { elt.prop[p][i] = elt.obj[i].style.backgroundColor; } |
|||
else if ( p === "display" ) { elt.prop[p][i] = elt.obj[i].style.display; } |
|||
} |
|||
} |
|||
}, |
|||
alterProp: function(elt) { |
|||
for (var i=0 ; i<elt.obj.length ; i++) { |
|||
for (var p in elt.prop) { |
|||
if ( p === "color" ) { elt.obj[i].style.color = "white"; } |
|||
else if ( p === "bgColor" ) { elt.obj[i].style.backgroundColor = "transparent"; } |
|||
else if ( p === "display" ) { elt.obj[i].style.display = "none"; } |
|||
} |
|||
} |
|||
}, |
|||
revertProp: function(elt) { |
|||
for (var i=0 ; i<elt.obj.length ; i++) { |
|||
for (var p in elt.prop) { |
|||
if ( p === "color" ) { elt.obj[i].style.color = elt.prop[p][i]; } |
|||
else if ( p === "bgColor" ) { elt.obj[i].style.backgroundColor = elt.prop[p][i]; } |
|||
else if ( p === "display" ) { elt.obj[i].style.display = elt.prop[p][i]; } |
|||
} |
|||
} |
|||
}, |
|||
|
|||
elts: { |
|||
alerts: { |
|||
obj: undefined, |
|||
prop: {bgColor: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "alert"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
btns: { |
|||
obj: undefined, |
|||
prop: {color: [], bgColor: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "btn"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
body: { |
|||
obj: undefined, |
|||
prop: {color: []}, |
|||
get: function(main) { |
|||
this.obj = document.body; |
|||
for (var p in this.prop) { if ( p === "color" ) { this.prop[p] = this.obj.style.color; } } |
|||
}, |
|||
alter: function() { for (var p in this.prop) { if ( p === "color" ) { this.obj.style.color = "white"; } } }, |
|||
revert: function() { for (var p in this.prop) { if ( p === "color" ) { this.obj.style.color = this.prop[p]; } } } |
|||
}, |
|||
captions: { |
|||
obj: undefined, |
|||
prop: {color: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "caption"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
helps: { |
|||
obj: undefined, |
|||
prop: {color: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "help-block"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
hrs: { |
|||
obj: undefined, |
|||
prop: {display: []}, |
|||
get: function(main) { sapphire.getTag(this, main, "hr"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
inputs: { |
|||
obj: undefined, |
|||
prop: {color: [], bgColor: []}, |
|||
get: function(main) { sapphire.getTag(this, main, "input"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
listGroups: { |
|||
obj: undefined, |
|||
prop: {color: [], bgColor: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "list-group-item"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
paginations: { |
|||
obj: [], |
|||
prop: {bgColor: []}, |
|||
get: function(main) { |
|||
var a = main.getElementsByClassName("pagination"); |
|||
for (var i=0 ; i<a.length ; i++) { |
|||
this.obj[i] = []; this.prop.bgColor[i] = []; |
|||
for (var j=0 ; j<a[i].children.length ; j++) { |
|||
this.obj[i][j] = a[i].children[j].children[0]; |
|||
this.prop.bgColor[i][j] = this.obj[i][j].style.backgroundColor; |
|||
} |
|||
} |
|||
}, |
|||
alter: function () { |
|||
for (var i=0 ; i<this.obj.length ; i++) |
|||
for (var j=0 ; j<this.obj[i].length ; j++) |
|||
for (var p in this.prop) |
|||
if ( p === "bgColor" ) { this.obj[i][j].style.backgroundColor = "transparent"; } |
|||
}, |
|||
revert: function() { |
|||
for (var i=0 ; i<this.obj.length ; i++) |
|||
for (var j=0 ; j<this.obj[i].length ; j++) |
|||
for (var p in this.prop) |
|||
if ( p === "bgColor" ) { this.obj[i][j].style.backgroundColor = this.prop[p][i][j]; } |
|||
} |
|||
}, |
|||
panelHeadings: { |
|||
obj: undefined, |
|||
prop: {bgColor: [], color: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "panel-heading"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
panels: { |
|||
obj: undefined, |
|||
prop: {bgColor: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "panel"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
selects: { |
|||
obj: undefined, |
|||
prop: {color: [], bgColor: []}, |
|||
get: function(main) { sapphire.getTag(this, main, "select"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
sidenavs: { |
|||
obj: undefined, |
|||
prop: {bgColor: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "sidenav"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
tds: { |
|||
obj: undefined, |
|||
prop: {bgColor: []}, |
|||
get: function(main) { sapphire.getTag(this, main, "td"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
thumbnails: { |
|||
obj: undefined, |
|||
prop: {bgColor: []}, |
|||
get: function(main) { sapphire.getClass(this, main, "thumbnail"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
}, |
|||
trs: { |
|||
obj: undefined, |
|||
prop: {bgColor: []}, |
|||
get: function(main) { sapphire.getTag(this, main, "tr"); sapphire.getProp(this); }, |
|||
alter: function() { sapphire.alterProp(this); }, |
|||
revert: function() { sapphire.revertProp(this); } |
|||
} |
|||
}, |
|||
|
|||
columns: undefined, |
|||
alpha: undefined, |
|||
drops: undefined, |
|||
canvas: undefined, |
|||
|
|||
init: function() { |
|||
var main = document.getElementById("main"); |
|||
for (var e in sapphire.elts) { sapphire.elts[e].get(main); } |
|||
}, |
|||
|
|||
resize: function() { |
|||
var ctx = sapphire.canvas.getContext("2d"); |
|||
var img = ctx.getImageData( 0, 0, sapphire.canvas.width, sapphire.canvas.height ); |
|||
sapphire.canvas.width = window.innerWidth; |
|||
sapphire.canvas.height = window.innerHeight; |
|||
ctx.fillStyle = "rgba(0, 0, 0, 1)"; |
|||
ctx.fillRect(0, 0, sapphire.canvas.width, sapphire.canvas.height); |
|||
ctx.putImageData( img, 0, 0 ); |
|||
sapphire.columns = sapphire.canvas.width/FONT_SIZE; |
|||
sapphire.alpha = Math.max( 0, Math.min( 1, TRAIL_TIME / ( sapphire.canvas.height/FONT_SIZE ) ) ); |
|||
var newDrops = []; |
|||
for(var x = 0; x < sapphire.columns; x++) { |
|||
if ( sapphire.drops && sapphire.drops[x] ) { newDrops[x] = sapphire.drops[x] } |
|||
else { |
|||
newDrops[x] = []; |
|||
var nb = Math.floor(Math.random()*MAX_CHAR); |
|||
for (var y = 0; y < nb; y++) |
|||
newDrops[x][y] = 0; |
|||
} |
|||
} |
|||
sapphire.drops = newDrops; |
|||
}, |
|||
|
|||
run: function() { |
|||
sapphire.canvas = document.createElement("canvas"); |
|||
document.body.appendChild(sapphire.canvas); |
|||
sapphire.canvas.style.position = "fixed"; |
|||
sapphire.canvas.style.zIndex = -1; |
|||
sapphire.canvas.style.left = 0; |
|||
sapphire.canvas.style.top = 0; |
|||
|
|||
var ctx = sapphire.canvas.getContext("2d"); |
|||
ctx.fillStyle = "rgba(0, 0, 0, 1)"; |
|||
ctx.fillRect(0, 0, sapphire.canvas.width, sapphire.canvas.height); |
|||
|
|||
function attenuateBackground() { |
|||
ctx.fillStyle = "rgba(0, 0, 0, "+sapphire.alpha+")"; |
|||
ctx.fillRect(0, 0, sapphire.canvas.width, sapphire.canvas.height); |
|||
} |
|||
|
|||
function drawMatrixRainDrop() { |
|||
ctx.fillStyle = RAIN_COLOR; |
|||
ctx.font = FONT_SIZE + "px arial"; |
|||
for(var i = 0; i < sapphire.drops.length; i++) { |
|||
for (var j = 0; j < sapphire.drops[i].length; j++) { |
|||
var text = CHARACTERS[Math.floor(Math.random()*CHARACTERS.length)]; |
|||
ctx.fillText(text, i*FONT_SIZE, sapphire.drops[i][j]*FONT_SIZE); |
|||
if(sapphire.drops[i][j]*FONT_SIZE > sapphire.canvas.height && Math.random() > 0.975) |
|||
sapphire.drops[i][j] = 0; |
|||
sapphire.drops[i][j]++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function drawEverything() { |
|||
attenuateBackground(); |
|||
drawMatrixRainDrop(); |
|||
} |
|||
|
|||
sapphire.resize(); |
|||
window.addEventListener('resize', sapphire.resize); |
|||
sapphire.triggerHandle = setInterval(drawEverything, 1000/FPS); |
|||
}, |
|||
|
|||
stop: function() { |
|||
window.removeEventListener('resize', sapphire.resize); |
|||
clearInterval(sapphire.triggerHandle); |
|||
sapphire.canvas.parentNode.removeChild(sapphire.canvas); |
|||
}, |
|||
|
|||
alterElts: function() { for (var e in sapphire.elts) { sapphire.elts[e].alter(main); } }, |
|||
revertElts: function() { for (var e in sapphire.elts) { sapphire.elts[e].revert(main); } }, |
|||
|
|||
activate: function() { |
|||
if (!sapphire.runOnce) { |
|||
sapphire.runOnce = true; |
|||
sapphire.init(); |
|||
} |
|||
if (!sapphire.activated) { |
|||
sapphire.activated = true; |
|||
sapphire.alterElts(); |
|||
sapphire.run() |
|||
} |
|||
else { |
|||
sapphire.activated = false; |
|||
sapphire.stop(); |
|||
sapphire.revertElts(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return sapphire; |
|||
} |
|||
|
|||
var s = Sapphire(); |
|||
Konami(s.activate); |
|||
|
|||
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2013-2014 Twitter, Inc |
|||
|
|||
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. |
|||
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
@ -0,0 +1,40 @@ |
|||
{% comment %} |
|||
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 Gabriel Détraz |
|||
Copyright © 2017 Goulven Kermarec |
|||
Copyright © 2017 Augustin Lemesle |
|||
|
|||
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. |
|||
{% endcomment %} |
|||
|
|||
<div class="form-group {% if field.form.errors %}{% if field.errors %}has-error{% else %}has-success{% endif %}{% endif %}"> |
|||
<label class="control-label" for="{{ field.id_for_label }}"> |
|||
{{ field.label }} |
|||
</label> |
|||
<div id="{{ field.auto_id }}" data-toggle="buttons"> |
|||
{% for val in field.field.choices %} |
|||
<label for="id_u_{{ val.0 }}" class="btn btn-default{% if val.0 in field.initial %} active{% endif %}"> |
|||
<input {% if val.0 in field.initial %}checked="checked" {% endif %}class="" id="id_u_{{ val.0 }}" name="{{ field.name }}" title="" type="checkbox" value="{{ val.0 }}" /> {{ val.1 }} |
|||
</label> |
|||
{% endfor %} |
|||
</div> |
|||
{% for error in field.errors %} |
|||
<div class="help-block">{{ error }}</div> |
|||
{% endfor %} |
|||
<div class="help-block">{{ field.help_text }}</div> |
|||
</div> |
|||
@ -0,0 +1,50 @@ |
|||
{% comment %} |
|||
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 Gabriel Détraz |
|||
Copyright © 2017 Goulven Kermarec |
|||
Copyright © 2017 Augustin Lemesle |
|||
|
|||
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. |
|||
{% endcomment %} |
|||
|
|||
{% load url_insert_param %} |
|||
|
|||
{% spaceless %} |
|||
<div style="display: flex; padding: 0;"> |
|||
{{ text }} |
|||
<div style="display: grid; font-size: 9px; line-height: 1; margin: auto 0;"> |
|||
{% if prefix %} |
|||
{% with prefix|add:'_'|add:col as colname %} |
|||
<a role="button" href="{% url_insert_param request.get_full_path col=colname order='asc' %}" title="{{ desc|default:"Tri croissant" }}"> |
|||
<span class="glyphicon glyphicon-triangle-top"></span> |
|||
</a> |
|||
<a role="button" href="{% url_insert_param request.get_full_path col=colname order='desc' %}" title="{{ desc|default:"Tri décroissant" }}"> |
|||
<span class="glyphicon glyphicon-triangle-bottom"></span> |
|||
</a> |
|||
{% endwith %} |
|||
{% else %} |
|||
<a role="button" href="{% url_insert_param request.get_full_path col=col order='asc' %}" title="{{ desc|default:"Tri croissant" }}"> |
|||
<span class="glyphicon glyphicon-triangle-top"></span> |
|||
</a> |
|||
<a role="button" href="{% url_insert_param request.get_full_path col=col order='desc' %}" title="{{ desc|default:"Tri décroissant" }}"> |
|||
<span class="glyphicon glyphicon-triangle-bottom"></span> |
|||
</a> |
|||
{% endif %} |
|||
</div> |
|||
</div> |
|||
{% endspaceless %} |
|||
@ -0,0 +1,19 @@ |
|||
{% if not 'accept_cookies' in request.COOKIES%} |
|||
<script> |
|||
function accept_cookie() { |
|||
var d = new Date(); |
|||
var expiration_time = 7 * 24 * 60 * 60 * 1000; // Accepte les cookies pendant 7 jours. |
|||
d.setTime(d.getTime() + expiration_time); |
|||
var expires = "expires="+ d.toUTCString(); |
|||
document.cookie = "accept_cookies=1;" + expires + ";path=/"; |
|||
var banner = document.getElementById("cookie_banner"); |
|||
banner.parentNode.removeChild(banner); |
|||
} |
|||
</script> |
|||
<div class="navbar text-center" id="cookie_banner"> |
|||
<p>Ce site utilise des cookies. En poursuivant sur ce site j'accepte l'utilisation des cookies sur ce site.</p> |
|||
<a class="btn btn-primary btn-sm" role="button" onclick="accept_cookie();" title="Accepter"> |
|||
J'ai compris ! |
|||
</a> |
|||
</div> |
|||
{% endif %} |
|||
@ -0,0 +1,40 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-15 18:33 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('topologie', '0030_auto_20171004_0235'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name='port', |
|||
name='port', |
|||
field=models.PositiveIntegerField(), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='stack', |
|||
name='member_id_max', |
|||
field=models.PositiveIntegerField(), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='stack', |
|||
name='member_id_min', |
|||
field=models.PositiveIntegerField(), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='switch', |
|||
name='number', |
|||
field=models.PositiveIntegerField(), |
|||
), |
|||
migrations.AlterField( |
|||
model_name='switch', |
|||
name='stack_member_id', |
|||
field=models.PositiveIntegerField(blank=True, null=True), |
|||
), |
|||
] |
|||
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Generated by Django 1.10.7 on 2017-10-26 01:38 |
|||
from __future__ import unicode_literals |
|||
|
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('topologie', '0031_auto_20171015_2033'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='ConstructorSwitch', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=255)), |
|||
], |
|||
), |
|||
migrations.CreateModel( |
|||
name='ModelSwitch', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('reference', models.CharField(max_length=255)), |
|||
('constructor', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='topologie.ConstructorSwitch')), |
|||
], |
|||
), |
|||
migrations.AddField( |
|||
model_name='switch', |
|||
name='model', |
|||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='topologie.ModelSwitch'), |
|||
), |
|||
] |
|||
@ -0,0 +1,54 @@ |
|||
{% comment %} |
|||
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 Gabriel Détraz |
|||
Copyright © 2017 Goulven Kermarec |
|||
Copyright © 2017 Augustin Lemesle |
|||
|
|||
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. |
|||
{% endcomment %} |
|||
|
|||
{% if constructor_switch_list.paginator %} |
|||
{% include "pagination.html" with list=constructor_switch_list %} |
|||
{% endif %} |
|||
|
|||
<table class="table table-striped"> |
|||
<thead> |
|||
<tr> |
|||
<th>{% include "buttons/sort.html" with prefix='constructor-switch' col='name' text='Constructeur' %}</th> |
|||
<th></th> |
|||
</tr> |
|||
</thead> |
|||
{% for constructor_switch in constructor_switch_list %} |
|||
<tr> |
|||
<td>{{constructor_switch}}</td> |
|||
<td class="text-right"> |
|||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'constructor_switch' constructor_switch.pk %}"> |
|||
<i class="glyphicon glyphicon-time"></i> |
|||
</a> |
|||
{% if is_infra %} |
|||
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-constructor-switch' constructor_switch.id %}"> |
|||
<i class="glyphicon glyphicon-edit"></i> |
|||
</a> |
|||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-constructor-switch' constructor_switch.id %}"> |
|||
<i class="glyphicon glyphicon-trash"></i> |
|||
</a> |
|||
{% endif %} |
|||
</td> |
|||
</tr> |
|||
{% endfor %} |
|||
</table> |
|||
@ -0,0 +1,56 @@ |
|||
{% comment %} |
|||
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 Gabriel Détraz |
|||
Copyright © 2017 Goulven Kermarec |
|||
Copyright © 2017 Augustin Lemesle |
|||
|
|||
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. |
|||
{% endcomment %} |
|||
|
|||
{% if model_switch_list.paginator %} |
|||
{% include "pagination.html" with list=model_switch_list %} |
|||
{% endif %} |
|||
|
|||
<table class="table table-striped"> |
|||
<thead> |
|||
<tr> |
|||
<th>{% include "buttons/sort.html" with prefix='model-switch' col='reference' text='Référence' %}</th> |
|||
<th>{% include "buttons/sort.html" with prefix='model-switch' col='constructor' text='Constructeur' %}</th> |
|||
<th></th> |
|||
</tr> |
|||
</thead> |
|||
{% for model_switch in model_switch_list %} |
|||
<tr> |
|||
<td>{{model_switch.reference}}</td> |
|||
<td>{{model_switch.constructor}}</td> |
|||
<td class="text-right"> |
|||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'model_switch' model_switch.pk %}"> |
|||
<i class="glyphicon glyphicon-time"></i> |
|||
</a> |
|||
{% if is_infra %} |
|||
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-model-switch' model_switch.id %}"> |
|||
<i class="glyphicon glyphicon-edit"></i> |
|||
</a> |
|||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-model-switch' model_switch.id %}"> |
|||
<i class="glyphicon glyphicon-trash"></i> |
|||
</a> |
|||
{% endif %} |
|||
</td> |
|||
</tr> |
|||
{% endfor %} |
|||
</table> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue