diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9020e1..f390ba09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,3 +120,11 @@ Don't forget to run migrations, several settings previously in the `preferences` in their own Payment models. To have a closer look on how the payments works, please go to the wiki. + +## MR 182: Add role models + +Adds the Role model. +You need to ensure that your database character set is utf-8. +```sql +ALTER DATABASE re2o CHARACTER SET utf8; +``` diff --git a/api/serializers.py b/api/serializers.py index 398f2b19..3a5f7f90 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -338,6 +338,7 @@ class OptionalMachineSerializer(NamespacedHMSerializer): class OptionalTopologieSerializer(NamespacedHMSerializer): """Serialize `preferences.models.OptionalTopologie` objects. """ + class Meta: model = preferences.OptionalTopologie fields = ('radius_general_policy', 'vlan_decision_ok', diff --git a/machines/admin.py b/machines/admin.py index 26d7a6a3..af721ff9 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -42,6 +42,7 @@ from .models import ( SshFp, Nas, Service, + Role, OuverturePort, Ipv6List, OuverturePortList, @@ -146,6 +147,11 @@ class ServiceAdmin(VersionAdmin): """ Admin view of a ServiceAdmin object """ list_display = ('service_type', 'min_time_regen', 'regular_time_regen') +class RoleAdmin(VersionAdmin): + """ Admin view of a RoleAdmin object """ + pass + + admin.site.register(Machine, MachineAdmin) admin.site.register(MachineType, MachineTypeAdmin) @@ -162,6 +168,7 @@ admin.site.register(IpList, IpListAdmin) admin.site.register(Interface, InterfaceAdmin) admin.site.register(Domain, DomainAdmin) admin.site.register(Service, ServiceAdmin) +admin.site.register(Role, RoleAdmin) admin.site.register(Vlan, VlanAdmin) admin.site.register(Ipv6List, Ipv6ListAdmin) admin.site.register(Nas, NasAdmin) diff --git a/machines/forms.py b/machines/forms.py index 23c2aa39..ecffcc71 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -37,6 +37,7 @@ from __future__ import unicode_literals from django.forms import ModelForm, Form from django import forms +from django.utils.translation import ugettext_lazy as _l from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin @@ -53,6 +54,7 @@ from .models import ( Txt, DName, Ns, + Role, Service, Vlan, Srv, @@ -497,6 +499,38 @@ class DelNasForm(FormRevMixin, Form): self.fields['nas'].queryset = Nas.objects.all() +class RoleForm(FormRevMixin, ModelForm): + """Add and edit role.""" + class Meta: + model = Role + fields = '__all__' + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(RoleForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields['servers'].queryset = (Interface.objects.all() + .select_related( + 'domain__extension' + )) + + +class DelRoleForm(FormRevMixin, Form): + """Deletion of one or several roles.""" + role = forms.ModelMultipleChoiceField( + queryset=Role.objects.none(), + label=_l("Current roles"), + widget=forms.CheckboxSelectMultiple + ) + + def __init__(self, *args, **kwargs): + instances = kwargs.pop('instances', None) + super(DelRoleForm, self).__init__(*args, **kwargs) + if instances: + self.fields['role'].queryset = instances + else: + self.fields['role'].queryset = Role.objects.all() + + class ServiceForm(FormRevMixin, ModelForm): """Ajout et edition d'une classe de service : dns, dhcp, etc""" class Meta: diff --git a/machines/migrations/0086_role.py b/machines/migrations/0086_role.py new file mode 100644 index 00000000..a23de26f --- /dev/null +++ b/machines/migrations/0086_role.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-06-23 14:07 +from __future__ import unicode_literals + +from django.db import migrations, models +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0085_sshfingerprint'), + ] + + operations = [ + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role_type', models.CharField(max_length=255, unique=True)), + ('servers', models.ManyToManyField(to='machines.Interface')), + ('specific_role', models.CharField(blank=True, choices=[('dhcp-server', 'DHCP server'), ('switch-conf-server', 'Switches configuration server'), ('dns-recursif-server', 'Recursive DNS server'), ('ntp-server', 'NTP server'), ('radius-server', 'Radius server'), ('log-server', 'Log server'), ('ldap-master-server', 'LDAP master server'), ('ldap-backup-server', 'LDAP backup server'), ('smtp-server', 'SMTP server'), ('postgresql-server', 'postgreSQL server'), ('mysql-server', 'mySQL server'), ('sql-client', 'SQL client'), ('gateway', 'Gatewaw')], max_length=32, null=True)) + ], + options={'permissions': (('view_role', 'Can view a role.'),), 'verbose_name': 'Server role'}, + bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model), + ), + ] diff --git a/machines/models.py b/machines/models.py index 7be76e74..e981bf10 100644 --- a/machines/models.py +++ b/machines/models.py @@ -42,6 +42,7 @@ from django.forms import ValidationError from django.utils.functional import cached_property from django.utils import timezone from django.core.validators import MaxValueValidator +from django.utils.translation import ugettext_lazy as _l from macaddress.fields import MACAddressField @@ -158,7 +159,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -176,7 +177,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -338,10 +339,10 @@ class IpType(RevMixin, AclMixin, models.Model): return else: for ipv6 in Ipv6List.objects.filter( - interface__in=Interface.objects.filter( - type__in=MachineType.objects.filter(ip_type=self) - ) - ): + interface__in=Interface.objects.filter( + type__in=MachineType.objects.filter(ip_type=self) + ) + ): ipv6.check_and_replace_prefix(prefix=self.prefix_v6) def clean(self): @@ -713,7 +714,7 @@ class Srv(RevMixin, AclMixin, models.Model): choices=( (TCP, 'TCP'), (UDP, 'UDP'), - ), + ), default=TCP, ) extension = models.ForeignKey('Extension', on_delete=models.PROTECT) @@ -1047,7 +1048,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1064,7 +1065,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1165,7 +1166,7 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1182,7 +1183,7 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): user_request, *args, **kwargs - )[0]): + )[0]): return False, (u"Vous ne pouvez pas éditer une machine " "d'un autre user que vous sans droit") return True, None @@ -1358,11 +1359,11 @@ class Domain(RevMixin, AclMixin, models.Model): return False, (u"Vous ne pouvez pas ajouter un alias à une " "machine d'un autre user que vous sans droit") if Domain.objects.filter( - cname__in=Domain.objects.filter( - interface_parent__in=(interface.machine.user - .user_interfaces()) - ) - ).count() >= max_lambdauser_aliases: + cname__in=Domain.objects.filter( + interface_parent__in=(interface.machine.user + .user_interfaces()) + ) + ).count() >= max_lambdauser_aliases: return False, (u"Vous avez atteint le maximum d'alias " "autorisés que vous pouvez créer vous même " "(%s) " % max_lambdauser_aliases) @@ -1441,6 +1442,75 @@ class IpList(RevMixin, AclMixin, models.Model): return self.ipv4 +class Role(RevMixin, AclMixin, models.Model): + """Define the role of a machine. + Allow automated generation of the server configuration. + """ + + ROLE = ( + ('dhcp-server', _l('DHCP server')), + ('switch-conf-server', _l('Switches configuration server')), + ('dns-recursif-server', _l('Recursive DNS server')), + ('ntp-server', _l('NTP server')), + ('radius-server', _l('Radius server')), + ('log-server', _l('Log server')), + ('ldap-master-server', _l('LDAP master server')), + ('ldap-backup-server', _l('LDAP backup server')), + ('smtp-server', _l('SMTP server')), + ('postgresql-server', _l('postgreSQL server')), + ('mysql-server', _l('mySQL server')), + ('sql-client', _l('SQL client')), + ('gateway', _l('Gatewaw')), + ) + + role_type = models.CharField(max_length=255, unique=True) + servers = models.ManyToManyField('Interface') + specific_role = models.CharField( + choices=ROLE, + null=True, + blank=True, + max_length=32, + ) + + class Meta: + permissions = ( + ("view_role", _l("Can view a role.")), + ) + verbose_name = _l("Server role") + + @classmethod + def get_instance(cls, roleid, *_args, **_kwargs): + """Get the Role instance with roleid. + + Args: + roleid: The id + + Returns: + The role. + """ + return cls.objects.get(pk=roleid) + + @classmethod + def interface_for_roletype(cls, roletype): + """Return interfaces for a roletype""" + return Interface.objects.filter( + role=cls.objects.filter(specific_role=roletype) + ) + + @classmethod + def all_interfaces_for_roletype(cls, roletype): + """Return all interfaces for a roletype""" + return Interface.objects.filter( + machine__interface__role=cls.objects.filter(specific_role=roletype) + ) + + def save(self, *args, **kwargs): + super(Role, self).save(*args, **kwargs) + + def __str__(self): + return str(self.role_type) + + class Service(RevMixin, AclMixin, models.Model): """ Definition d'un service (dhcp, dns, etc)""" PRETTY_NAME = "Services à générer (dhcp, dns, etc)" @@ -1471,8 +1541,8 @@ class Service(RevMixin, AclMixin, models.Model): """ Django ne peut créer lui meme les relations manytomany avec table intermediaire explicite""" for serv in servers.exclude( - pk__in=Interface.objects.filter(service=self) - ): + pk__in=Interface.objects.filter(service=self) + ): link = Service_link(service=self, server=serv) link.save() Service_link.objects.filter(service=self).exclude(server__in=servers)\ @@ -1630,7 +1700,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model): choices=( (TCP, 'TCP'), (UDP, 'UDP'), - ), + ), default=TCP, ) io = models.CharField( @@ -1638,7 +1708,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model): choices=( (IN, 'IN'), (OUT, 'OUT'), - ), + ), default=OUT, ) diff --git a/machines/templates/machines/aff_role.html b/machines/templates/machines/aff_role.html new file mode 100644 index 00000000..519e8fd6 --- /dev/null +++ b/machines/templates/machines/aff_role.html @@ -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 %} + +{% load acl %} +{% load i18n %} +{% load logs_extra %} + + +
{% trans "Role name" %} | +{% trans "Specific role" %} | +{% trans "Servers" %} | ++ | + |
---|---|---|---|---|
{{ role.role_type }} | +{{ role.specific_role }} | +{% for serv in role.servers.all %}{{ serv }}, {% endfor %} | ++ {% can_edit role %} + {% include 'buttons/edit.html' with href='machines:edit-role' id=role.id %} + {% acl_end %} + {% history_button role %} + | +