8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-25 22:22:26 +00:00

Merge branch 'fix_stack' into 'master'

Fix stack

Closes #104 and #108

See merge request federez/re2o!140
This commit is contained in:
chirac 2018-04-16 18:26:04 +02:00
commit 5453b845e2
14 changed files with 2249 additions and 71 deletions

View file

@ -27,9 +27,16 @@ from __future__ import unicode_literals
from django.forms import ModelForm, Form
from django import forms
from .models import OptionalUser, OptionalMachine, OptionalTopologie
from .models import GeneralOption, AssoOption, MailMessageOption, Service
from .models import (
OptionalUser,
OptionalMachine,
OptionalTopologie,
GeneralOption,
AssoOption,
MailMessageOption,
AccueilOption,
Service
)
class EditOptionalUserForm(ModelForm):
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
@ -185,6 +192,21 @@ class EditMailMessageOptionForm(ModelForm):
mail de bienvenue en anglais'
class EditAccueilOptionForm(ModelForm):
"""Formulaire d'édition des options de la page d'accueil"""
class Meta:
model = AccueilOption
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditAccueilOptionForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
class ServiceForm(ModelForm):
"""Edition, ajout de services sur la page d'accueil"""
class Meta:

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-04-16 02:35
from __future__ import unicode_literals
from django.db import migrations, models
import re2o.mixins
class Migration(migrations.Migration):
dependencies = [
('preferences', '0032_optionaluser_shell_default'),
]
operations = [
migrations.CreateModel(
name='AccueilOption',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('facebook_url', models.URLField(blank=True, help_text='Url du compte facebook', null=True)),
('twitter_url', models.URLField(blank=True, help_text='Url du compte twitter', null=True)),
('twitter_account_name', models.CharField(blank=True, help_text='Nom du compte à afficher', max_length=32, null=True)),
],
options={
'permissions': (('view_accueiloption', "Peut voir les options de l'accueil"),),
},
bases=(re2o.mixins.AclMixin, models.Model),
),
]

View file

@ -331,6 +331,40 @@ def assooption_post_save(**kwargs):
asso_pref.set_in_cache()
class AccueilOption(AclMixin, PreferencesModel):
"""Reglages de la page d'accueil"""
PRETTY_NAME = "Options de la page d'accueil"
facebook_url = models.URLField(
null=True,
blank=True,
help_text="Url du compte facebook"
)
twitter_url = models.URLField(
null=True,
blank=True,
help_text="Url du compte twitter"
)
twitter_account_name = models.CharField(
max_length=32,
null=True,
blank=True,
help_text="Nom du compte à afficher"
)
class Meta:
permissions = (
("view_accueiloption", "Peut voir les options de l'accueil"),
)
@receiver(post_save, sender=AccueilOption)
def accueiloption_post_save(**kwargs):
"""Ecriture dans le cache"""
accueil_pref = kwargs['instance']
accueil_pref.set_in_cache()
class MailMessageOption(AclMixin, models.Model):
"""Reglages, mail de bienvenue et autre"""
PRETTY_NAME = "Options de corps de mail"

View file

@ -211,12 +211,31 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ mailmessageoptions.welcome_mail_en | safe }}</td>
</tr>
</table>
<h2>Liste des services page d'accueil</h2>
<h2>Liste des services et préférences page d'accueil</h2>
{% can_create preferences.Service%}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-service' %}"><i class="fa fa-plus"></i> Ajouter un service</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'preferences:del-services' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs service</a>
{% include "preferences/aff_service.html" with service_list=service_list %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'AccueilOption' %}">
<i class="fa fa-edit"></i>
Editer
</a>
<p>
<table class="table table-striped">
<tr>
<th>Url du compte twitter</th>
<td>{{ accueiloptions.twitter_url }}</td>
<th>Nom utilisé pour afficher le compte</th>
<td>{{ accueiloptions.twitter_account_name }}</td>
</tr>
<tr>
<th>Url du compte facebook</th>
<td>{{ accueiloptions.facebook_url }}</td>
</tr>
</table>
<br />
<br />
<br />

View file

@ -57,6 +57,11 @@ urlpatterns = [
views.edit_options,
name='edit-options'
),
url(
r'^edit_options/(?P<section>AccueilOption)$',
views.edit_options,
name='edit-options'
),
url(
r'^edit_options/(?P<section>MailMessageOption)$',
views.edit_options,

View file

@ -43,8 +43,16 @@ from re2o.views import form
from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
from .forms import ServiceForm, DelServiceForm
from .models import Service, OptionalUser, OptionalMachine, AssoOption
from .models import MailMessageOption, GeneralOption, OptionalTopologie
from .models import (
Service,
OptionalUser,
OptionalMachine,
AssoOption,
MailMessageOption,
GeneralOption,
OptionalTopologie,
AccueilOption
)
from . import models
from . import forms
@ -56,6 +64,7 @@ from . import forms
@can_view_all(GeneralOption)
@can_view_all(AssoOption)
@can_view_all(MailMessageOption)
@can_view_all(AccueilOption)
def display_options(request):
"""Vue pour affichage des options (en vrac) classé selon les models
correspondants dans un tableau"""
@ -64,6 +73,7 @@ def display_options(request):
topologieoptions, _created = OptionalTopologie.objects.get_or_create()
generaloptions, _created = GeneralOption.objects.get_or_create()
assooptions, _created = AssoOption.objects.get_or_create()
accueiloptions, _created = AccueilOption.objects.get_or_create()
mailmessageoptions, _created = MailMessageOption.objects.get_or_create()
service_list = Service.objects.all()
return form({
@ -72,6 +82,7 @@ def display_options(request):
'topologieoptions': topologieoptions,
'generaloptions': generaloptions,
'assooptions': assooptions,
'accueiloptions': accueiloptions,
'mailmessageoptions': mailmessageoptions,
'service_list': service_list
}, 'preferences/display_preferences.html', request)

View file

@ -25,4 +25,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block sidebar %}
{% if facebook_url %}
<div class="fb-page" data-href="{{ facebook_url }}" data-tabs="timeline" data-small-header="false" data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="false"><blockquote cite="{{ facebook_url }}" class="fb-xfbml-parse-ignore"><a href="{{ facebook_url }}">{{ asso_name }}</a></blockquote></div>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = 'https://connect.facebook.net/fr_FR/sdk.js#xfbml=1&version=v2.12';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<hr>
{% endif %}
{% if twitter_url %}
<a class="twitter-timeline" data-lang="fr" data-height="500" href="{{ twitter_url }}?ref_src=twsrc%5Etfw">Tweets de @{{ twitter_account_name }}</a>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<a href="{{ twitter_url }}?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false"> Suivre @{{ twitter_account_name }}</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
{% endif %}
{% endblock %}

View file

@ -41,7 +41,12 @@ from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_page
import preferences
from preferences.models import Service, GeneralOption, AssoOption
from preferences.models import (
Service,
GeneralOption,
AssoOption,
AccueilOption
)
import users
import cotisations
import topologie
@ -63,7 +68,17 @@ def index(request):
services = [[], [], []]
for indice, serv in enumerate(Service.objects.all()):
services[indice % 3].append(serv)
return form({'services_urls': services}, 're2o/index.html', request)
twitter_url = AccueilOption.get_cached_value('twitter_url')
facebook_url = AccueilOption.get_cached_value('facebook_url')
twitter_account_name = AccueilOption.get_cached_value('twitter_account_name')
asso_name = AssoOption.get_cached_value('pseudo')
return form({
'services_urls': services,
'twitter_url': twitter_url,
'twitter_account_name' : twitter_account_name,
'facebook_url': facebook_url,
'asso_name': asso_name
}, 're2o/index.html', request)
#: Binding the corresponding char sequence of history url to re2o models.

1929
static/js/jquery.ez-plus.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -31,58 +31,31 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% include "buttons/sort.html" with prefix='stack' col='id' text='ID' %}</th>
<th>Détails</th>
<th>Membres</th>
<th></th>
</tr>
</thead>
{% for stack in stack_list %}
{% for switch in stack.switch_set.all %}
<tbody>
<tr class="active">
{% if forloop.first %}
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.name}}</td>
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.stack_id}}</td>
<td rowspan="{{ stack.switch_set.all|length }}" >{{stack.details}}</td>
{% endif %}
<td><a href="{% url 'topologie:index-port' switch.pk %}">{{switch}}</a></td>
{% if forloop.first %}
<td rowspan="{{ stack.switch_set.all|length }}">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="fa fa-history"></i>
</a>
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="fa fa-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
</td>
{% endif %}
</tr>
{% empty %}
<tr class="active">
<td>{{stack.name}}</td>
<td>{{stack.stack_id}}</td>
<td>{{stack.details}}</td>
<td>Aucun</td>
<td>
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="fa fa-history"></i>
</a>
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="fa fa-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
</td>
{% endfor %}
</tbody>
{% endfor %}
</thead>
{% for stack in stack_list %}
<tr>
<td>{{ stack.name }}</td>
<td>{{stack.stack_id}}</td>
<td>{{stack.details}}</td>
<td>{% for switch in stack.switch_set.all %}<a href="{% url 'topologie:index-port' switch.pk %}">{{switch }} </a>{% endfor %}</td>
<td class="text-right">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
<i class="fa fa-history"></i>
</a>
{% can_edit stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<i class="fa fa-edit"></i>
</a>
{% acl_end %}
{% can_delete stack %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load acl %}
<div class="table-responsive">
{% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %}
{% endif %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
@ -72,8 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
{% endfor %}
</table>
</div>
{% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %}
{% endif %}
</div>

View file

@ -43,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{switch_bay.name}}</td>
<td>{{switch_bay.building}}</td>
<td>{{switch_bay.info}}</td>
<td>{{switch_bay.switch_set.all |join:", "}}</td>
<td>{% for switch in switch_bay.switch_set.all %}<a href="{% url 'topologie:index-port' switch.pk %}">{{switch }} </a>{% endfor %}</td>
<td class="text-right">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'switchbay' switch_bay.pk %}">
<i class="fa fa-history"></i>

View file

@ -29,7 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block title %}Switchs{% endblock %}
{% block content %}
<h2>Switchs</h2>
<img id="zoom_01" src="/media/images/switchs.png" data-zoom-image="/media/images/switchs.png" width=100% />
<script type="text/javascript" src="/static/js/jquery.ez-plus.js"></script>
<script>
$("#zoom_01").ezPlus({
scrollZoom: true,
zoomType: 'inner',
cursor: 'crosshair'
});
</script>
<h2>Switchs</h2>
{% can_create Switch %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-switch' %}"><i class="fa fa-plus"></i> Ajouter un switch</a>
<hr>
@ -38,4 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<br />
<br />
<br />
{% endblock %}

View file

@ -42,6 +42,7 @@ from django.contrib.auth.decorators import login_required
from django.db import IntegrityError
from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError
from django.contrib.staticfiles.storage import staticfiles_storage
from users.views import form
from re2o.utils import re2o_paginator, SortTable
@ -88,6 +89,8 @@ from .forms import (
EditBuildingForm
)
from subprocess import Popen,PIPE
@login_required
@can_view_all(Switch)
@ -349,6 +352,7 @@ def new_stack(request):
if stack.is_valid():
stack.save()
messages.success(request, "Stack crée")
return redirect(reverse('topologie:index-physical-grouping'))
return form(
{'topoform': stack, 'action_name': 'Créer'},
'topologie/topo.html',
@ -364,7 +368,7 @@ def edit_stack(request, stack, **_kwargs):
if stack.is_valid():
if stack.changed_data:
stack.save()
return redirect(reverse('topologie:index-physical-grouping'))
return redirect(reverse('topologie:index-physical-grouping'))
return form(
{'topoform': stack, 'action_name': 'Editer'},
'topologie/topo.html',
@ -926,8 +930,103 @@ def del_constructor_switch(request, constructor_switch, **_kwargs):
"de la supprimer (switch ou user)" % constructor_switch)
)
return redirect(reverse('topologie:index-model-switch'))
return form(
{'objet': constructor_switch, 'objet_name': 'Constructeur de switch'},
'topologie/delete.html',
request
)
return form({
'objet': constructor_switch,
'objet_name': 'Constructeur de switch'
}, 'topologie/delete.html', request)
def make_machine_graph():
"""
Crée le fichier dot et l'image du graph des Switchs
"""
#Syntaxe DOT temporaire, A mettre dans un template:
lignes=['''digraph Switchs {
node [
fontname=Helvetica
fontsize=8
shape=plaintext]
edge[arrowhead=odot,arrowtail=dot]''']
node_fixe='''node [label=<
<TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4">
<FONT FACE="Helvetica Bold" COLOR="white">
{}
</FONT></TD></TR>
<TR><TD ALIGN="LEFT" BORDER="0">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD></TR>
<TR><TD ALIGN="LEFT" BORDER="0">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT>{}</FONT>
</TD></TR>'''
node_ports='''<TR><TD ALIGN="LEFT" BORDER="0">
<FONT COLOR="#7B7B7B" >{}</FONT>
</TD>
<TD ALIGN="LEFT">
<FONT>{}</FONT>
</TD></TR>'''
cluster='''subgraph cluster_{} {{
color=blue;
label="Batiment {}";'''
end_table='''</TABLE>
>] \"{}_{}\" ;'''
switch_alone='''{} [label=<
<TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4">
<FONT FACE="Helvetica Bold" COLOR="white">
{}
</FONT></TD></TR>
</TABLE>
>]'''
missing=[]
detected=[]
for sw in Switch.objects.all():
if(sw not in detected):
missing.append(sw)
for building in Building.objects.all():
lignes.append(cluster.format(len(lignes),building))
for switch in Switch.objects.filter(switchbay__building=building):
lignes.append(node_fixe.format(switch.main_interface().domain.name,"Modèle",switch.model,"Nombre de ports",switch.number))
for p in switch.ports.all().filter(related__isnull=False):
lignes.append(node_ports.format(p.port,p.related.switch.main_interface().domain.name))
lignes.append(end_table.format(building.id,switch.id))
lignes.append("}")
while(missing!=[]):
lignes,new_detected=recursive_switchs(missing[0].ports.all().filter(related=None).first(),None,lignes,[missing[0]])
missing=[i for i in missing if i not in new_detected]
detected+=new_detected
for switch in Switch.objects.all().filter(switchbay__isnull=True).exclude(ports__related__isnull=False):
lignes.append(switch_alone.format(switch.id,switch.main_interface().domain.name))
lignes.append("}")
fichier = open("media/images/switchs.dot","w")
for ligne in lignes:
fichier.write(ligne+"\n")
fichier.close()
unflatten = Popen(["unflatten","-l", "3", "media/images/switchs.dot"], stdout=PIPE)
image = Popen(["dot", "-Tpng", "-o", "media/images/switchs.png"], stdin=unflatten.stdout, stdout=PIPE)
def recursive_switchs(port_start, switch_before, lignes,detected):
"""
Parcour récursivement le switchs auquel appartient port_start pour trouver les ports suivants liés
"""
l_ports=port_start.switch.ports.filter(related__isnull=False)
for port in l_ports:
if port.related.switch!=switch_before and port.related.switch!=port.switch:
links=[]
for sw in [switch for switch in [port_start.switch,port.related.switch]]:
if(sw not in detected):
detected.append(sw)
if(sw.switchbay.building):
links.append("\"{}_{}\"".format(sw.switchbay.building.id,sw.id))
else:
links.append("\"{}\"".format(sw.id))
lignes.append(links[0]+" -> "+links[1])
lignes, detected = recursive_switchs(port.related, port_start.switch, lignes, detected)
return (lignes, detected)