diff --git a/re2o/base.py b/re2o/base.py index fff2278c..1397b1b0 100644 --- a/re2o/base.py +++ b/re2o/base.py @@ -194,12 +194,17 @@ class SortTable: } TOPOLOGIE_INDEX_ROOM = { 'room_name': ['name'], - 'default': ['name'] + 'building_name': ['building__name'], + 'default': ['building__name', 'name'] } TOPOLOGIE_INDEX_BUILDING = { 'building_name': ['name'], 'default': ['name'] } + TOPOLOGIE_INDEX_DORMITORY = { + 'dormitory_name': ['name'], + 'default': ['name'] + } TOPOLOGIE_INDEX_BORNE = { 'ap_name': ['interface__domain__name'], 'ap_ip': ['interface__ipv4__ipv4'], diff --git a/topologie/admin.py b/topologie/admin.py index d2a25461..d6fa318e 100644 --- a/topologie/admin.py +++ b/topologie/admin.py @@ -39,6 +39,7 @@ from .models import ( AccessPoint, SwitchBay, Building, + Dormitory, PortProfile, ) @@ -87,6 +88,12 @@ class BuildingAdmin(VersionAdmin): """Administration d'un batiment""" pass + +class DormitoryAdmin(VersionAdmin): + """Administration d'une residence""" + pass + + class PortProfileAdmin(VersionAdmin): """Administration of a port profile""" pass @@ -99,5 +106,6 @@ admin.site.register(Stack, StackAdmin) admin.site.register(ModelSwitch, ModelSwitchAdmin) admin.site.register(ConstructorSwitch, ConstructorSwitchAdmin) admin.site.register(Building, BuildingAdmin) +admin.site.register(Dormitory, DormitoryAdmin) admin.site.register(SwitchBay, SwitchBayAdmin) admin.site.register(PortProfile, PortProfileAdmin) diff --git a/topologie/forms.py b/topologie/forms.py index ed6fa5b9..bdc8ba4e 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -54,6 +54,7 @@ from .models import ( AccessPoint, SwitchBay, Building, + Dormitory, PortProfile, ModuleSwitch, ModuleOnSwitch, @@ -259,6 +260,18 @@ class EditBuildingForm(FormRevMixin, ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(EditBuildingForm, self).__init__(*args, prefix=prefix, **kwargs) + +class EditDormitoryForm(FormRevMixin, ModelForm): + """Enable dormitory edition""" + class Meta: + model = Dormitory + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(EditDormitoryForm, self).__init__(*args, prefix=prefix, **kwargs) + + class EditPortProfileForm(FormRevMixin, ModelForm): """Form to edit a port profile""" class Meta: diff --git a/topologie/migrations/0070_auto_20190218_1743.py b/topologie/migrations/0070_auto_20190218_1743.py new file mode 100644 index 00000000..bb0c6f5c --- /dev/null +++ b/topologie/migrations/0070_auto_20190218_1743.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2019-02-18 16:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0069_auto_20190108_1439'), + ] + + def create_dormitory(apps, schema_editor): + db_alias = schema_editor.connection.alias + dormitory = apps.get_model("topologie", "Dormitory") + building = apps.get_model("topologie", "Building") + dorm = dormitory.objects.using(db_alias).create(name="Residence") + building.objects.using(db_alias).update(dormitory=dorm) + + def delete_dormitory(apps, schema_editor): + pass + + operations = [ + migrations.CreateModel( + name='Dormitory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + options={ + 'verbose_name': 'dormitory', + 'permissions': (('view_dormitory', 'Can view a dormitory object'),), + 'verbose_name_plural': 'dormitories', + }, + bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model), + ), + migrations.AddField( + model_name='building', + name='dormitory', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.Dormitory'), + preserve_default=False, + ), + migrations.RunPython(create_dormitory, delete_dormitory) + ] diff --git a/topologie/migrations/0071_auto_20190218_1936.py b/topologie/migrations/0071_auto_20190218_1936.py new file mode 100644 index 00000000..8c5e3c8d --- /dev/null +++ b/topologie/migrations/0071_auto_20190218_1936.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2019-02-18 18:36 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topologie', '0070_auto_20190218_1743'), + ] + + def transfer_room(apps, schema_editor): + db_alias = schema_editor.connection.alias + room_obj = apps.get_model("topologie", "Room") + building_obj = apps.get_model("topologie", "Building") + dorm_obj = apps.get_model("topologie", "Dormitory") + dorm = dorm_obj.objects.using(db_alias).first() + for room in room_obj.objects.using(db_alias).all(): + building, created = building_obj.objects.using(db_alias).get_or_create(name=room.name[0].upper(), dormitory=dorm) + room.building = building + room.name = room.name[1:] + room.save() + + def untransfer_room(apps, schema_editor): + pass + + operations = [ + migrations.AlterField( + model_name='room', + name='name', + field=models.CharField(max_length=255), + ), + migrations.AddField( + model_name='room', + name='building', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.Building'), + ), + migrations.AlterUniqueTogether( + name='room', + unique_together=set([('name', 'building')]), + ), + migrations.AlterField( + model_name='building', + name='dormitory', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='topologie.Dormitory'), + ), + migrations.RunPython(transfer_room, untransfer_room), + migrations.AlterField( + model_name='room', + name='building', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='topologie.Building'), + ) + ] diff --git a/topologie/models.py b/topologie/models.py index 3e093b40..82a9c6a8 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -516,11 +516,38 @@ class SwitchBay(AclMixin, RevMixin, models.Model): return self.name -class Building(AclMixin, RevMixin, models.Model): - """Un batiment""" +class Dormitory(AclMixin, RevMixin, models.Model): + """A student accomodation/dormitory + Une résidence universitaire""" name = models.CharField(max_length=255) + + class Meta: + permissions = ( + ("view_dormitory", _("Can view a dormitory object")), + ) + verbose_name = _("dormitory") + verbose_name_plural = _("dormitories") + + def all_ap_in(self): + """Returns all ap of the dorms""" + return AccessPoint.all_ap_in(self.building_set.all()) + + def __str__(self): + return self.name + + +class Building(AclMixin, RevMixin, models.Model): + """A building of a dormitory + Un batiment""" + + name = models.CharField(max_length=255) + dormitory = models.ForeignKey( + 'Dormitory', + on_delete=models.PROTECT, + ) + class Meta: permissions = ( ("view_building", _("Can view a building object")), @@ -532,8 +559,15 @@ class Building(AclMixin, RevMixin, models.Model): """Returns all ap of the building""" return AccessPoint.all_ap_in(self) + @cached_property + def cached_name(self): + return self.__str__() + def __str__(self): - return self.name + if Dormitory.objects.count() > 1: + return self.dormitory.name + " : " + self.name + else: + return self.name class Port(AclMixin, RevMixin, models.Model): @@ -703,19 +737,24 @@ class Port(AclMixin, RevMixin, models.Model): class Room(AclMixin, RevMixin, models.Model): """Une chambre/local contenant une prise murale""" - name = models.CharField(max_length=255, unique=True) + name = models.CharField(max_length=255) details = models.CharField(max_length=255, blank=True) + building = models.ForeignKey( + 'Building', + on_delete=models.PROTECT, + ) class Meta: - ordering = ['name'] + ordering = ['building__name'] permissions = ( ("view_room", _("Can view a room object")), ) verbose_name = _("room") verbose_name_plural = _("rooms") + unique_together = ('name', 'building') def __str__(self): - return self.name + return self.building.cached_name + self.name class PortProfile(AclMixin, RevMixin, models.Model): diff --git a/topologie/templates/topologie/aff_building.html b/topologie/templates/topologie/aff_building.html index 9885f1c5..5775bb28 100644 --- a/topologie/templates/topologie/aff_building.html +++ b/topologie/templates/topologie/aff_building.html @@ -35,13 +35,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Building" as tr_building %} {% include 'buttons/sort.html' with prefix='building' col='name' text=tr_building %} - {% trans "Access points" %} + {% trans "Access points" %} {% for building in building_list %} - {{ building.name }} + {{ building.cached_name }} {% for ap in building.all_ap_in %} {{ ap.short_name }} {% endfor %} {% can_edit building %} diff --git a/topologie/templates/topologie/aff_chambres.html b/topologie/templates/topologie/aff_chambres.html index 6e2b181f..4a79efbd 100644 --- a/topologie/templates/topologie/aff_chambres.html +++ b/topologie/templates/topologie/aff_chambres.html @@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Room" as tr_room %} + {% trans "Building" as tr_building %} + {% include 'buttons/sort.html' with prefix='building' col='name' text=tr_building %} {% include 'buttons/sort.html' with prefix='room' col='name' text=tr_room %} {% trans "Details" %} @@ -41,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for room in room_list %} + {{ room.building }} {{ room.name }} {{ room.details }} diff --git a/topologie/templates/topologie/aff_dormitory.html b/topologie/templates/topologie/aff_dormitory.html new file mode 100644 index 00000000..758e5e2c --- /dev/null +++ b/topologie/templates/topologie/aff_dormitory.html @@ -0,0 +1,62 @@ +{% 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 acl %} +{% load logs_extra %} +{% load i18n %} + +{% if dormitory_list.paginator %} + {% include 'pagination.html' with list=dormitory_list %} +{% endif %} + + + + + {% trans "Dormitory" as tr_dormitory %} + + + + + + {% for dormitory in dormitory_list %} + + + + + + {% endfor %} +
{% include 'buttons/sort.html' with prefix='dormitory' col='name' text=tr_dormitory %}{% trans "Building" %}
{{ dormitory.name }}{% for building in dormitory.building_set.all %} {{ building.name }} {% endfor %} + {% can_edit dormitory %} + {% include 'buttons/edit.html' with href='topologie:edit-dormitory' id=dormitory.id %} + {% acl_end %} + {% history_button dormitory %} + {% can_delete dormitory %} + {% include 'buttons/suppr.html' with href='topologie:del-dormitory' id=dormitory.id %} + {% acl_end %} +
+ +{% if dormitory_list.paginator %} + {% include 'pagination.html' with list=dormitory_list %} +{% endif %} + diff --git a/topologie/templates/topologie/index_physical_grouping.html b/topologie/templates/topologie/index_physical_grouping.html index d2defa0c..7b081439 100644 --- a/topologie/templates/topologie/index_physical_grouping.html +++ b/topologie/templates/topologie/index_physical_grouping.html @@ -55,5 +55,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% acl_end %} {% include 'topologie/aff_building.html' with building_list=building_list %} + + +

{% trans "Dormitories" %}

+ {% can_create Dormitory %} + + {% trans " Add a dormitory" %} + +
+ {% acl_end %} + {% include 'topologie/aff_dormitory.html' with dormitory_list=dormitory_list %} {% endblock %} diff --git a/topologie/urls.py b/topologie/urls.py index 70eae8e4..76cf089c 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -108,6 +108,15 @@ urlpatterns = [ url(r'^del_building/(?P[0-9]+)$', views.del_building, name='del-building'), + url(r'^new_dormitory/$', + views.new_dormitory, + name='new-dormitory'), + url(r'^edit_dormitory/(?P[0-9]+)$', + views.edit_dormitory, + name='edit-dormitory'), + url(r'^del_dormitory/(?P[0-9]+)$', + views.del_dormitory, + name='del-dormitory'), url(r'^index_port_profile/$', views.index_port_profile, name='index-port-profile'), diff --git a/topologie/views.py b/topologie/views.py index 2eeb3d55..201c8eb4 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -84,6 +84,7 @@ from .models import ( AccessPoint, SwitchBay, Building, + Dormitory, Server, PortProfile, ModuleSwitch, @@ -103,6 +104,7 @@ from .forms import ( EditAccessPointForm, EditSwitchBayForm, EditBuildingForm, + EditDormitoryForm, EditPortProfileForm, EditModuleForm, EditSwitchModuleForm, @@ -254,7 +256,7 @@ def index_ap(request): @login_required -@can_view_all(Stack, Building, SwitchBay) +@can_view_all(Stack, Building, Dormitory, SwitchBay) def index_physical_grouping(request): """Affichage de la liste des stacks (affiche l'ensemble des switches)""" stack_list = (Stack.objects @@ -262,6 +264,7 @@ def index_physical_grouping(request): 'switch_set__interface_set__domain__extension' )) building_list = Building.objects.all() + dormitory_list = Dormitory.objects.all() switch_bay_list = SwitchBay.objects.select_related('building') stack_list = SortTable.sort( stack_list, @@ -275,6 +278,12 @@ def index_physical_grouping(request): request.GET.get('order'), SortTable.TOPOLOGIE_INDEX_BUILDING ) + dormitory_list = SortTable.sort( + dormitory_list, + request.GET.get('col'), + request.GET.get('order'), + SortTable.TOPOLOGIE_INDEX_DORMITORY + ) switch_bay_list = SortTable.sort( switch_bay_list, request.GET.get('col'), @@ -288,6 +297,7 @@ def index_physical_grouping(request): 'stack_list': stack_list, 'switch_bay_list': switch_bay_list, 'building_list': building_list, + 'dormitory_list': dormitory_list, } ) @@ -904,7 +914,8 @@ def del_switch_bay(request, switch_bay, **_kwargs): @login_required @can_create(Building) def new_building(request): - """Nouveau batiment""" + """New Building of a dorm + Nouveau batiment""" building = EditBuildingForm(request.POST or None) if building.is_valid(): building.save() @@ -920,7 +931,8 @@ def new_building(request): @login_required @can_edit(Building) def edit_building(request, building, **_kwargs): - """ Edition d'un batiment""" + """Edit a building + Edition d'un batiment""" building = EditBuildingForm(request.POST or None, instance=building) if building.is_valid(): if building.changed_data: @@ -937,7 +949,8 @@ def edit_building(request, building, **_kwargs): @login_required @can_delete(Building) def del_building(request, building, **_kwargs): - """ Suppression d'un batiment""" + """Delete a building + Suppression d'un batiment""" if request.method == "POST": try: building.delete() @@ -956,6 +969,64 @@ def del_building(request, building, **_kwargs): ) +@login_required +@can_create(Dormitory) +def new_dormitory(request): + """A new dormitory + Nouvelle residence""" + dormitory = EditDormitoryForm(request.POST or None) + if dormitory.is_valid(): + dormitory.save() + messages.success(request, _("The dormitory was created.")) + return redirect(reverse('topologie:index-physical-grouping')) + return form( + {'topoform': dormitory, 'action_name': _("Create")}, + 'topologie/topo.html', + request + ) + + +@login_required +@can_edit(Dormitory) +def edit_dormitory(request, dormitory, **_kwargs): + """Edit a dormitory + Edition d'une residence""" + dormitory = EditDormitoryForm(request.POST or None, instance=dormitory) + if dormitory.is_valid(): + if dormitory.changed_data: + dormitory.save() + messages.success(request, _("The dormitory was edited.")) + return redirect(reverse('topologie:index-physical-grouping')) + return form( + {'topoform': dormitory, 'action_name': _("Edit")}, + 'topologie/topo.html', + request + ) + + +@login_required +@can_delete(Dormitory) +def del_dormitory(request, dormitory, **_kwargs): + """Delete a dormitory + Suppression d'une residence""" + if request.method == "POST": + try: + dormitory.delete() + messages.success(request, _("The dormitory was deleted.")) + except ProtectedError: + messages.error( + request, + (_("The dormitory %s is used by another object, impossible" + " to delete it.") % dormitory) + ) + return redirect(reverse('topologie:index-physical-grouping')) + return form( + {'objet': dormitory, 'objet_name': _("Dormitory")}, + 'topologie/delete.html', + request + ) + + @login_required @can_create(ConstructorSwitch) def new_constructor_switch(request):