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.forms import ModelForm, Form
from django import forms from django import forms
from .models import OptionalUser, OptionalMachine, OptionalTopologie from .models import (
from .models import GeneralOption, AssoOption, MailMessageOption, Service OptionalUser,
OptionalMachine,
OptionalTopologie,
GeneralOption,
AssoOption,
MailMessageOption,
AccueilOption,
Service
)
class EditOptionalUserForm(ModelForm): class EditOptionalUserForm(ModelForm):
"""Formulaire d'édition des options de l'user. (solde, telephone..)""" """Formulaire d'édition des options de l'user. (solde, telephone..)"""
@ -185,6 +192,21 @@ class EditMailMessageOptionForm(ModelForm):
mail de bienvenue en anglais' 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): class ServiceForm(ModelForm):
"""Edition, ajout de services sur la page d'accueil""" """Edition, ajout de services sur la page d'accueil"""
class Meta: 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() 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): class MailMessageOption(AclMixin, models.Model):
"""Reglages, mail de bienvenue et autre""" """Reglages, mail de bienvenue et autre"""
PRETTY_NAME = "Options de corps de mail" 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> <td>{{ mailmessageoptions.welcome_mail_en | safe }}</td>
</tr> </tr>
</table> </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%} {% 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> <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 %} {% 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> <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 %} {% 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 /> <br />
<br /> <br />

View file

@ -57,6 +57,11 @@ urlpatterns = [
views.edit_options, views.edit_options,
name='edit-options' name='edit-options'
), ),
url(
r'^edit_options/(?P<section>AccueilOption)$',
views.edit_options,
name='edit-options'
),
url( url(
r'^edit_options/(?P<section>MailMessageOption)$', r'^edit_options/(?P<section>MailMessageOption)$',
views.edit_options, 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 re2o.acl import can_create, can_edit, can_delete_set, can_view_all
from .forms import ServiceForm, DelServiceForm from .forms import ServiceForm, DelServiceForm
from .models import Service, OptionalUser, OptionalMachine, AssoOption from .models import (
from .models import MailMessageOption, GeneralOption, OptionalTopologie Service,
OptionalUser,
OptionalMachine,
AssoOption,
MailMessageOption,
GeneralOption,
OptionalTopologie,
AccueilOption
)
from . import models from . import models
from . import forms from . import forms
@ -56,6 +64,7 @@ from . import forms
@can_view_all(GeneralOption) @can_view_all(GeneralOption)
@can_view_all(AssoOption) @can_view_all(AssoOption)
@can_view_all(MailMessageOption) @can_view_all(MailMessageOption)
@can_view_all(AccueilOption)
def display_options(request): def display_options(request):
"""Vue pour affichage des options (en vrac) classé selon les models """Vue pour affichage des options (en vrac) classé selon les models
correspondants dans un tableau""" correspondants dans un tableau"""
@ -64,6 +73,7 @@ def display_options(request):
topologieoptions, _created = OptionalTopologie.objects.get_or_create() topologieoptions, _created = OptionalTopologie.objects.get_or_create()
generaloptions, _created = GeneralOption.objects.get_or_create() generaloptions, _created = GeneralOption.objects.get_or_create()
assooptions, _created = AssoOption.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() mailmessageoptions, _created = MailMessageOption.objects.get_or_create()
service_list = Service.objects.all() service_list = Service.objects.all()
return form({ return form({
@ -72,6 +82,7 @@ def display_options(request):
'topologieoptions': topologieoptions, 'topologieoptions': topologieoptions,
'generaloptions': generaloptions, 'generaloptions': generaloptions,
'assooptions': assooptions, 'assooptions': assooptions,
'accueiloptions': accueiloptions,
'mailmessageoptions': mailmessageoptions, 'mailmessageoptions': mailmessageoptions,
'service_list': service_list 'service_list': service_list
}, 'preferences/display_preferences.html', request) }, '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 %} {% 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 %} {% endblock %}

View file

@ -41,7 +41,12 @@ from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
import preferences import preferences
from preferences.models import Service, GeneralOption, AssoOption from preferences.models import (
Service,
GeneralOption,
AssoOption,
AccueilOption
)
import users import users
import cotisations import cotisations
import topologie import topologie
@ -63,7 +68,17 @@ def index(request):
services = [[], [], []] services = [[], [], []]
for indice, serv in enumerate(Service.objects.all()): for indice, serv in enumerate(Service.objects.all()):
services[indice % 3].append(serv) 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. #: 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>{% include "buttons/sort.html" with prefix='stack' col='id' text='ID' %}</th>
<th>Détails</th> <th>Détails</th>
<th>Membres</th> <th>Membres</th>
<th></th>
</tr> </tr>
</thead> </thead>
{% for stack in stack_list %} {% for stack in stack_list %}
{% for switch in stack.switch_set.all %} <tr>
<tbody> <td>{{ stack.name }}</td>
<tr class="active"> <td>{{stack.stack_id}}</td>
{% if forloop.first %} <td>{{stack.details}}</td>
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.name}}</td> <td>{% for switch in stack.switch_set.all %}<a href="{% url 'topologie:index-port' switch.pk %}">{{switch }} </a>{% endfor %}</td>
<td rowspan="{{ stack.switch_set.all|length }}">{{stack.stack_id}}</td> <td class="text-right">
<td rowspan="{{ stack.switch_set.all|length }}" >{{stack.details}}</td> <a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}">
{% endif %} <i class="fa fa-history"></i>
<td><a href="{% url 'topologie:index-port' switch.pk %}">{{switch}}</a></td> </a>
{% if forloop.first %} {% can_edit stack %}
<td rowspan="{{ stack.switch_set.all|length }}"> <a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'stack' stack.pk %}"> <i class="fa fa-edit"></i>
<i class="fa fa-history"></i> </a>
</a> {% acl_end %}
{% can_edit stack %} {% can_delete stack %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-stack' stack.id %}"> <a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}">
<i class="fa fa-edit"></i> <i class="fa fa-trash"></i>
</a> </a>
{% acl_end %} {% acl_end %}
{% can_delete stack %} </td>
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-stack' stack.pk %}"> </tr>
<i class="fa fa-trash"></i> {% endfor %}
</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 %}
</table> </table>

View file

@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load acl %} {% load acl %}
<div class="table-responsive">
{% if switch_list.paginator %} {% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %} {% include "pagination.html" with list=switch_list %}
{% endif %} {% endif %}
<div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
@ -72,8 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
</div>
{% if switch_list.paginator %} {% if switch_list.paginator %}
{% include "pagination.html" with list=switch_list %} {% include "pagination.html" with list=switch_list %}
{% endif %} {% 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.name}}</td>
<td>{{switch_bay.building}}</td> <td>{{switch_bay.building}}</td>
<td>{{switch_bay.info}}</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"> <td class="text-right">
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'switchbay' switch_bay.pk %}"> <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> <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 title %}Switchs{% endblock %}
{% block content %} {% 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 %} {% 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> <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> <hr>
@ -38,4 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<br /> <br />
<br /> <br />
<br /> <br />
{% endblock %} {% endblock %}

View file

@ -42,6 +42,7 @@ from django.contrib.auth.decorators import login_required
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import ProtectedError, Prefetch from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.staticfiles.storage import staticfiles_storage
from users.views import form from users.views import form
from re2o.utils import re2o_paginator, SortTable from re2o.utils import re2o_paginator, SortTable
@ -88,6 +89,8 @@ from .forms import (
EditBuildingForm EditBuildingForm
) )
from subprocess import Popen,PIPE
@login_required @login_required
@can_view_all(Switch) @can_view_all(Switch)
@ -349,6 +352,7 @@ def new_stack(request):
if stack.is_valid(): if stack.is_valid():
stack.save() stack.save()
messages.success(request, "Stack crée") messages.success(request, "Stack crée")
return redirect(reverse('topologie:index-physical-grouping'))
return form( return form(
{'topoform': stack, 'action_name': 'Créer'}, {'topoform': stack, 'action_name': 'Créer'},
'topologie/topo.html', 'topologie/topo.html',
@ -364,7 +368,7 @@ def edit_stack(request, stack, **_kwargs):
if stack.is_valid(): if stack.is_valid():
if stack.changed_data: if stack.changed_data:
stack.save() stack.save()
return redirect(reverse('topologie:index-physical-grouping')) return redirect(reverse('topologie:index-physical-grouping'))
return form( return form(
{'topoform': stack, 'action_name': 'Editer'}, {'topoform': stack, 'action_name': 'Editer'},
'topologie/topo.html', 'topologie/topo.html',
@ -926,8 +930,103 @@ def del_constructor_switch(request, constructor_switch, **_kwargs):
"de la supprimer (switch ou user)" % constructor_switch) "de la supprimer (switch ou user)" % constructor_switch)
) )
return redirect(reverse('topologie:index-model-switch')) return redirect(reverse('topologie:index-model-switch'))
return form( return form({
{'objet': constructor_switch, 'objet_name': 'Constructeur de switch'}, 'objet': constructor_switch,
'topologie/delete.html', 'objet_name': 'Constructeur de switch'
request }, '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)