From d5d4e2192b6209fa587427d085a1e78850c23fdf Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 21 Jul 2017 05:06:53 +0200 Subject: [PATCH] Deplace les checks dans les models + liaison entre ip_type et ip_list ranges --- machines/forms.py | 15 +- .../migrations/0043_auto_20170721_0350.py | 25 ++++ machines/models.py | 80 ++++++++++- machines/views.py | 128 ++++++++---------- topologie/views.py | 90 ++++++------ 5 files changed, 204 insertions(+), 134 deletions(-) create mode 100644 machines/migrations/0043_auto_20170721_0350.py diff --git a/machines/forms.py b/machines/forms.py index c4faac97..66997aa4 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -74,7 +74,7 @@ class AddInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).filter(need_infra=False) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) else: self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) @@ -92,7 +92,7 @@ class BaseEditInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).filter(need_infra=False) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) else: self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) @@ -152,14 +152,13 @@ class IpTypeForm(ModelForm): super(IpTypeForm, self).__init__(*args, **kwargs) self.fields['type'].label = 'Type ip à ajouter' -class DelIpTypeForm(ModelForm): +class EditIpTypeForm(IpTypeForm): + class Meta(IpTypeForm.Meta): + fields = ['extension','type','need_infra'] + +class DelIpTypeForm(forms.Form): iptypes = forms.ModelMultipleChoiceField(queryset=IpType.objects.all(), label="Types d'ip actuelles", widget=forms.CheckboxSelectMultiple) - class Meta: - exclude = ['type','extension','need_infra','domaine_ip','domaine_range'] - model = IpType - - class ExtensionForm(ModelForm): class Meta: model = Extension diff --git a/machines/migrations/0043_auto_20170721_0350.py b/machines/migrations/0043_auto_20170721_0350.py new file mode 100644 index 00000000..497367bf --- /dev/null +++ b/machines/migrations/0043_auto_20170721_0350.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-07-21 01:50 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0042_ns_ns'), + ] + + operations = [ + migrations.RemoveField( + model_name='iplist', + name='need_infra', + ), + migrations.AlterField( + model_name='iplist', + name='ip_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='machines.IpType'), + ), + ] diff --git a/machines/models.py b/machines/models.py index e2a0eb09..a424b788 100644 --- a/machines/models.py +++ b/machines/models.py @@ -21,13 +21,14 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.db import models -from django.db.models.signals import post_save, post_delete +from django.db.models.signals import post_save, pre_delete, post_delete from django.dispatch import receiver from django.forms import ValidationError -from macaddress.fields import MACAddressField -from netaddr import mac_bare, EUI -from django.core.validators import MinValueValidator,MaxValueValidator from django.utils.functional import cached_property +from macaddress.fields import MACAddressField +from netaddr import mac_bare, EUI, IPSet, IPNetwork +from django.core.validators import MinValueValidator,MaxValueValidator +import re from re2o.settings import MAIN_EXTENSION @@ -59,6 +60,48 @@ class IpType(models.Model): domaine_ip = models.GenericIPAddressField(protocol='IPv4') domaine_range = models.IntegerField(validators=[MinValueValidator(8), MaxValueValidator(32)]) + @cached_property + def network(self): + return str(self.domaine_ip) + '/' + str(self.domaine_range) + + @cached_property + def ip_network(self): + return IPNetwork(self.network) + + @cached_property + def ip_set(self): + return IPSet(self.ip_network) + + @cached_property + def ip_set_as_str(self): + return [str(x) for x in self.ip_set] + + @cached_property + def ip_objects(self): + return IpList.objects.filter(ipv4__in=self.ip_set_as_str) + + def gen_ip_range(self): + # Creation du range d'ip dans les objets iplist + for ip in self.ip_network.iter_hosts(): + obj, created = IpList.objects.get_or_create(ip_type=self, ipv4=str(ip)) + + def del_ip_range(self): + if Interface.objects.filter(ipv4__in=self.ip_objects): + raise ValidationError("Une ou plusieurs ip du range sont affectées, impossible de supprimer le range") + for ip in self.ip_objects(): + ip.delete() + + def clean(self): + # On check que les / ne se recoupent pas + for element in IpType.objects.all(): + if not self.ip_set.isdisjoint(element.ip_set): + raise ValidationError("Le range indiqué n'est pas disjoint des ranges existants") + return + + def save(self, *args, **kwargs): + self.clean() + super(IpType, self).save(*args, **kwargs) + def __str__(self): return self.type @@ -133,10 +176,18 @@ class Domain(models.Model): unique_together = ("name", "extension") def clean(self): + """ Validation du nom de domaine, extensions dans type de machine, prefixe pas plus long que 63 caractères """ if self.interface_parent and self.cname: raise ValidationError("On ne peut créer à la fois A et CNAME") if self.cname==self: raise ValidationError("On ne peut créer un cname sur lui même") + HOSTNAME_LABEL_PATTERN = re.compile("(?!-)[A-Z\d-]+(? 63: + raise ValidationError("Le nom de domaine %s est trop long (maximum de 63 caractères)." % dns) + if not HOSTNAME_LABEL_PATTERN.match(dns): + raise ValidationError("Ce nom de domaine %s contient des carractères interdits." % dns) + return def __str__(self): return str(self.name) + str(self.extension) @@ -145,8 +196,20 @@ class IpList(models.Model): PRETTY_NAME = "Addresses ipv4" ipv4 = models.GenericIPAddressField(protocol='IPv4', unique=True) - ip_type = models.ForeignKey('IpType', on_delete=models.PROTECT) - need_infra = models.BooleanField(default=False) + ip_type = models.ForeignKey('IpType', on_delete=models.CASCADE) + + @cached_property + def need_infra(self): + return self.ip_type.need_infra + + def clean(self): + if not str(self.ipv4) in self.ip_type.ip_set_as_str: + raise ValidationError("L'ipv4 et le range de l'iptype ne correspondent pas!") + return + + def save(self, *args, **kwargs): + self.clean() + super(IpList, self).save(*args, **kwargs) def __str__(self): return self.ipv4 @@ -173,3 +236,8 @@ def interface_post_delete(sender, **kwargs): user = interface.machine.user user.ldap_sync(base=False, access_refresh=False, mac_refresh=True) +@receiver(post_save, sender=IpType) +def iptype_post_save(sender, **kwargs): + iptype = kwargs['instance'] + iptype.gen_ip_range() + diff --git a/machines/views.py b/machines/views.py index c3778a21..3742deaf 100644 --- a/machines/views.py +++ b/machines/views.py @@ -44,26 +44,12 @@ from reversion.models import Version import re from .forms import NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm, MachineTypeForm, DelMachineTypeForm, ExtensionForm, DelExtensionForm, BaseEditInterfaceForm, BaseEditMachineForm -from .forms import IpTypeForm, DelIpTypeForm, AliasForm, DelAliasForm, NsForm, DelNsForm, MxForm, DelMxForm +from .forms import EditIpTypeForm, IpTypeForm, DelIpTypeForm, AliasForm, DelAliasForm, NsForm, DelNsForm, MxForm, DelMxForm from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain from users.models import User from users.models import all_has_access from preferences.models import GeneralOption, OptionalMachine -def full_domain_validator(request, domain): - """ Validation du nom de domaine, extensions dans type de machine, prefixe pas plus long que 63 caractères """ - HOSTNAME_LABEL_PATTERN = re.compile("(?!-)[A-Z\d-]+(? 63: - messages.error(request, - "Le nom de domaine %s est trop long (maximum de 63 caractères)." % dns) - return False - if not HOSTNAME_LABEL_PATTERN.match(dns): - messages.error(request, - "Ce nom de domaine %s contient des carractères interdits." % dns) - return False - return True - def all_active_interfaces(): """Renvoie l'ensemble des machines autorisées à sortir sur internet """ return Interface.objects.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() @@ -99,10 +85,7 @@ def assign_ips(user): def free_ip(type): """ Renvoie la liste des ip disponibles """ - if not type.need_infra: - return IpList.objects.filter(interface__isnull=True).filter(ip_type=type).filter(need_infra=False) - else: - return IpList.objects.filter(interface__isnull=True).filter(ip_type=type) + return IpList.objects.filter(interface__isnull=True).filter(ip_type=type) def assign_ipv4(interface): """ Assigne une ip à l'interface """ @@ -148,27 +131,26 @@ def new_machine(request, userid): new_machine.user = user new_interface = interface.save(commit=False) new_domain = domain.save(commit=False) - if full_domain_validator(request, new_domain): - with transaction.atomic(), reversion.create_revision(): - new_machine.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - new_interface.machine = new_machine - if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: - new_interface = assign_ipv4(new_interface) - elif not new_interface.ipv4: - messages.error(request, u"Il n'y a plus d'ip disponibles") - with transaction.atomic(), reversion.create_revision(): - new_interface.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - new_domain.interface_parent = new_interface - with transaction.atomic(), reversion.create_revision(): - new_domain.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - messages.success(request, "La machine a été crée") - return redirect("/users/profil/" + str(user.id)) + with transaction.atomic(), reversion.create_revision(): + new_machine.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_interface.machine = new_machine + if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: + new_interface = assign_ipv4(new_interface) + elif not new_interface.ipv4: + messages.error(request, u"Il n'y a plus d'ip disponibles") + with transaction.atomic(), reversion.create_revision(): + new_interface.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_domain.interface_parent = new_interface + with transaction.atomic(), reversion.create_revision(): + new_domain.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "La machine a été crée") + return redirect("/users/profil/" + str(user.id)) except TypeError: messages.error(request, u"Adresse mac invalide") return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'machines/machine.html', request) @@ -195,23 +177,22 @@ def edit_interface(request, interfaceid): new_interface = interface_form.save(commit=False) new_machine = machine_form.save(commit=False) new_domain = domain_form.save(commit=False) - if full_domain_validator(request, new_domain): - with transaction.atomic(), reversion.create_revision(): - new_machine.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data)) - if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: - new_interface = assign_ipv4(new_interface) - with transaction.atomic(), reversion.create_revision(): - new_interface.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data)) - with transaction.atomic(), reversion.create_revision(): - new_domain.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) - messages.success(request, "La machine a été modifiée") - return redirect("/users/profil/" + str(interface.machine.user.id)) + with transaction.atomic(), reversion.create_revision(): + new_machine.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data)) + if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: + new_interface = assign_ipv4(new_interface) + with transaction.atomic(), reversion.create_revision(): + new_interface.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data)) + with transaction.atomic(), reversion.create_revision(): + new_domain.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) + messages.success(request, "La machine a été modifiée") + return redirect("/users/profil/" + str(interface.machine.user.id)) except TypeError: messages.error(request, u"Adresse mac invalide") return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) @@ -258,22 +239,21 @@ def new_interface(request, machineid): new_interface = interface_form.save(commit=False) new_interface.machine = machine new_domain = domain_form.save(commit=False) - if full_domain_validator(request, new_domain): - if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: - new_interface = assign_ipv4(new_interface) - elif not new_interface.ipv4: - messages.error(request, u"Il n'y a plus d'ip disponibles") - with transaction.atomic(), reversion.create_revision(): - new_interface.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - new_domain.interface_parent = new_interface - with transaction.atomic(), reversion.create_revision(): - new_domain.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - messages.success(request, "L'interface a été ajoutée") - return redirect("/users/profil/" + str(machine.user.id)) + if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: + new_interface = assign_ipv4(new_interface) + elif not new_interface.ipv4: + messages.error(request, u"Il n'y a plus d'ip disponibles") + with transaction.atomic(), reversion.create_revision(): + new_interface.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_domain.interface_parent = new_interface + with transaction.atomic(), reversion.create_revision(): + new_domain.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "L'interface a été ajoutée") + return redirect("/users/profil/" + str(machine.user.id)) except TypeError: messages.error(request, u"Adresse mac invalide") return form({'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) @@ -321,7 +301,7 @@ def edit_iptype(request, iptypeid): except IpType.DoesNotExist: messages.error(request, u"Entrée inexistante" ) return redirect("/machines/index_iptype/") - iptype = IpTypeForm(request.POST or None, instance=iptype_instance) + iptype = EditIpTypeForm(request.POST or None, instance=iptype_instance) if iptype.is_valid(): with transaction.atomic(), reversion.create_revision(): iptype.save() diff --git a/topologie/views.py b/topologie/views.py index d7b4a1b8..8e8eaa48 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -35,7 +35,7 @@ from users.views import form from users.models import User from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm -from machines.views import free_ip, full_domain_validator, assign_ipv4 +from machines.views import free_ip, assign_ipv4 from preferences.models import GeneralOption from re2o.settings import ASSO_PSEUDO @@ -161,32 +161,31 @@ def new_switch(request): new_interface = interface.save(commit=False) new_switch = switch.save(commit=False) new_domain = domain.save(commit=False) - if full_domain_validator(request, new_domain): - with transaction.atomic(), reversion.create_revision(): - new_machine.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - new_interface.machine = new_machine - if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: - new_interface = assign_ipv4(new_interface) - elif not new_interface.ipv4: - messages.error(request, "Il n'y a plus d'ip disponibles") - with transaction.atomic(), reversion.create_revision(): - new_interface.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - new_domain.interface_parent = new_interface - with transaction.atomic(), reversion.create_revision(): - new_domain.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - new_switch.switch_interface = new_interface - with transaction.atomic(), reversion.create_revision(): - new_switch.save() - reversion.set_user(request.user) - reversion.set_comment("Création") - messages.success(request, "Le switch a été crée") - return redirect("/topologie/") + with transaction.atomic(), reversion.create_revision(): + new_machine.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_interface.machine = new_machine + if free_ip(new_interface.type.ip_type) and not new_interface.ipv4: + new_interface = assign_ipv4(new_interface) + elif not new_interface.ipv4: + messages.error(request, "Il n'y a plus d'ip disponibles") + with transaction.atomic(), reversion.create_revision(): + new_interface.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_domain.interface_parent = new_interface + with transaction.atomic(), reversion.create_revision(): + new_domain.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + new_switch.switch_interface = new_interface + with transaction.atomic(), reversion.create_revision(): + new_switch.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "Le switch a été crée") + return redirect("/topologie/") return form({'topoform':switch, 'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'topologie/switch.html', request) @login_required @@ -206,25 +205,24 @@ def edit_switch(request, switch_id): new_machine = machine_form.save(commit=False) new_switch = switch_form.save(commit=False) new_domain = domain_form.save(commit=False) - if full_domain_validator(request, new_domain): - with transaction.atomic(), reversion.create_revision(): - new_machine.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data)) - with transaction.atomic(), reversion.create_revision(): - new_interface.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data)) - with transaction.atomic(), reversion.create_revision(): - new_domain.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) - with transaction.atomic(), reversion.create_revision(): - new_switch.save() - reversion.set_user(request.user) - reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in switch_form.changed_data)) - messages.success(request, "Le switch a bien été modifié") - return redirect("/topologie/") + with transaction.atomic(), reversion.create_revision(): + new_machine.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in machine_form.changed_data)) + with transaction.atomic(), reversion.create_revision(): + new_interface.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in interface_form.changed_data)) + with transaction.atomic(), reversion.create_revision(): + new_domain.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) + with transaction.atomic(), reversion.create_revision(): + new_switch.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in switch_form.changed_data)) + messages.success(request, "Le switch a bien été modifié") + return redirect("/topologie/") return form({'topoform':switch_form, 'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'topologie/switch.html', request) @login_required