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

Pep8 et nettoyage

This commit is contained in:
Gabriel Detraz 2017-10-15 22:10:33 +02:00 committed by root
parent 643c8235b0
commit a9dcc6d9c4

View file

@ -23,45 +23,60 @@
from __future__ import unicode_literals
from datetime import timedelta
import re
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from django.db import models
from django.db.models.signals import post_save, pre_delete, post_delete
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.forms import ValidationError
from django.utils.functional import cached_property
from django.utils import timezone
from django.core.validators import MaxValueValidator
from macaddress.fields import MACAddressField
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
from django.core.validators import MinValueValidator,MaxValueValidator
import re
from reversion import revisions as reversion
from datetime import timedelta
class Machine(models.Model):
""" Class définissant une machine, object parent user, objets fils interfaces"""
""" Class définissant une machine, object parent user, objets fils
interfaces"""
PRETTY_NAME = "Machine"
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
name = models.CharField(max_length=255, help_text="Optionnel", blank=True, null=True)
name = models.CharField(
max_length=255,
help_text="Optionnel",
blank=True,
null=True
)
active = models.BooleanField(default=True)
def __str__(self):
return str(self.user) + ' - ' + str(self.id) + ' - ' + str(self.name)
class MachineType(models.Model):
""" Type de machine, relié à un type d'ip, affecté aux interfaces"""
PRETTY_NAME = "Type de machine"
type = models.CharField(max_length=255)
ip_type = models.ForeignKey('IpType', on_delete=models.PROTECT, blank=True, null=True)
ip_type = models.ForeignKey(
'IpType',
on_delete=models.PROTECT,
blank=True,
null=True
)
def all_interfaces(self):
""" Renvoie toutes les interfaces (cartes réseaux) de type machinetype"""
""" Renvoie toutes les interfaces (cartes réseaux) de type
machinetype"""
return Interface.objects.filter(type=self)
def __str__(self):
return self.type
class IpType(models.Model):
""" Type d'ip, définissant un range d'ip, affecté aux machine types"""
PRETTY_NAME = "Type d'ip"
@ -71,9 +86,22 @@ class IpType(models.Model):
need_infra = models.BooleanField(default=False)
domaine_ip_start = models.GenericIPAddressField(protocol='IPv4')
domaine_ip_stop = models.GenericIPAddressField(protocol='IPv4')
prefix_v6 = models.GenericIPAddressField(protocol='IPv6', null=True, blank=True)
vlan = models.ForeignKey('Vlan', on_delete=models.PROTECT, blank=True, null=True)
ouverture_ports = models.ForeignKey('OuverturePortList', blank=True, null=True)
prefix_v6 = models.GenericIPAddressField(
protocol='IPv6',
null=True,
blank=True
)
vlan = models.ForeignKey(
'Vlan',
on_delete=models.PROTECT,
blank=True,
null=True
)
ouverture_ports = models.ForeignKey(
'OuverturePortList',
blank=True,
null=True
)
@cached_property
def ip_range(self):
@ -96,18 +124,22 @@ class IpType(models.Model):
def free_ip(self):
""" Renvoie toutes les ip libres associées au type donné (self)"""
return IpList.objects.filter(interface__isnull=True).filter(ip_type=self)
return IpList.objects.filter(
interface__isnull=True
).filter(ip_type=self)
def gen_ip_range(self):
""" Cree les IpList associées au type self. Parcours pédestrement et crée
les ip une par une. Si elles existent déjà, met à jour le type associé
à l'ip"""
""" Cree les IpList associées au type self. Parcours pédestrement et
crée les ip une par une. Si elles existent déjà, met à jour le type
associé à l'ip"""
# Creation du range d'ip dans les objets iplist
networks = []
for net in self.ip_range.cidrs():
networks += net.iter_hosts()
ip_obj = [IpList(ip_type=self, ipv4=str(ip)) for ip in networks]
listes_ip = IpList.objects.filter(ipv4__in=[str(ip) for ip in networks])
listes_ip = IpList.objects.filter(
ipv4__in=[str(ip) for ip in networks]
)
# Si il n'y a pas d'ip, on les crée
if not listes_ip:
IpList.objects.bulk_create(ip_obj)
@ -117,9 +149,11 @@ class IpType(models.Model):
return
def del_ip_range(self):
""" Methode dépréciée, IpList est en mode cascade et supprimé automatiquement"""
""" Methode dépréciée, IpList est en mode cascade et supprimé
automatiquement"""
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")
raise ValidationError("Une ou plusieurs ip du range sont\
affectées, impossible de supprimer le range")
for ip in self.ip_objects():
ip.delete()
@ -133,11 +167,13 @@ class IpType(models.Model):
raise ValidationError("Domaine end doit être après start...")
# On ne crée pas plus grand qu'un /16
if self.ip_range.size > 65536:
raise ValidationError("Le range est trop gros, vous ne devez pas créer plus grand qu'un /16")
raise ValidationError("Le range est trop gros, vous ne devez\
pas créer plus grand qu'un /16")
# On check que les / ne se recoupent pas
for element in IpType.objects.all().exclude(pk=self.pk):
if not self.ip_set.isdisjoint(element.ip_set):
raise ValidationError("Le range indiqué n'est pas disjoint des ranges existants")
raise ValidationError("Le range indiqué n'est pas disjoint\
des ranges existants")
# On formate le prefix v6
if self.prefix_v6:
self.prefix_v6 = str(IPNetwork(self.prefix_v6 + '/64').network)
@ -150,6 +186,7 @@ class IpType(models.Model):
def __str__(self):
return self.type
class Vlan(models.Model):
""" Un vlan : vlan_id et nom
On limite le vlan id entre 0 et 4096, comme défini par la norme"""
@ -162,6 +199,7 @@ class Vlan(models.Model):
def __str__(self):
return self.name
class Nas(models.Model):
""" Les nas. Associé à un machine_type.
Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour
@ -175,22 +213,40 @@ class Nas(models.Model):
)
name = models.CharField(max_length=255, unique=True)
nas_type = models.ForeignKey('MachineType', on_delete=models.PROTECT, related_name='nas_type')
machine_type = models.ForeignKey('MachineType', on_delete=models.PROTECT, related_name='machinetype_on_nas')
port_access_mode = models.CharField(choices=AUTH, default=default_mode, max_length=32)
nas_type = models.ForeignKey(
'MachineType',
on_delete=models.PROTECT,
related_name='nas_type'
)
machine_type = models.ForeignKey(
'MachineType',
on_delete=models.PROTECT,
related_name='machinetype_on_nas'
)
port_access_mode = models.CharField(
choices=AUTH,
default=default_mode,
max_length=32
)
autocapture_mac = models.BooleanField(default=False)
def __str__(self):
return self.name
class Extension(models.Model):
""" Extension dns type example.org. Précise si tout le monde peut l'utiliser,
associé à un origin (ip d'origine)"""
""" Extension dns type example.org. Précise si tout le monde peut
l'utiliser, associé à un origin (ip d'origine)"""
PRETTY_NAME = "Extensions dns"
name = models.CharField(max_length=255, unique=True)
need_infra = models.BooleanField(default=False)
origin = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True)
origin = models.OneToOneField(
'IpList',
on_delete=models.PROTECT,
blank=True,
null=True
)
@cached_property
def dns_entry(self):
@ -200,8 +256,10 @@ class Extension(models.Model):
def __str__(self):
return self.name
class Mx(models.Model):
""" Entrées des MX. Enregistre la zone (extension) associée et la priorité
""" Entrées des MX. Enregistre la zone (extension) associée et la
priorité
Todo : pouvoir associer un MX à une interface """
PRETTY_NAME = "Enregistrements MX"
@ -211,12 +269,16 @@ class Mx(models.Model):
@cached_property
def dns_entry(self):
"""Renvoie l'entrée DNS complète pour un MX à mettre dans les
fichiers de zones"""
return "@ IN MX " + str(self.priority) + " " + str(self.name)
def __str__(self):
return str(self.zone) + ' ' + str(self.priority) + ' ' + str(self.name)
class Ns(models.Model):
"""Liste des enregistrements name servers par zone considéérée"""
PRETTY_NAME = "Enregistrements NS"
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
@ -224,11 +286,13 @@ class Ns(models.Model):
@cached_property
def dns_entry(self):
"""Renvoie un enregistrement NS complet pour les filezones"""
return "@ IN NS " + str(self.ns)
def __str__(self):
return str(self.zone) + ' ' + str(self.ns)
class Text(models.Model):
""" Un enregistrement TXT associé à une extension"""
PRETTY_NAME = "Enregistrement text"
@ -238,22 +302,31 @@ class Text(models.Model):
field2 = models.CharField(max_length=255)
def __str__(self):
return str(self.zone) + " : " + str(self.field1) + " " + str(self.field2)
return str(self.zone) + " : " + str(self.field1) + " " +\
str(self.field2)
@cached_property
def dns_entry(self):
"""Renvoie l'enregistrement TXT complet pour le fichier de zone"""
return str(self.field1) + " IN TXT " + str(self.field2)
class Interface(models.Model):
""" Une interface. Objet clef de l'application machine :
- une address mac unique. Possibilité de la rendre unique avec le typemachine
- une address mac unique. Possibilité de la rendre unique avec le
typemachine
- une onetoone vers IpList pour attribution ipv4
- le type parent associé au range ip et à l'extension
- un objet domain associé contenant son nom
- la liste des ports oiuvert"""
PRETTY_NAME = "Interface"
ipv4 = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True)
ipv4 = models.OneToOneField(
'IpList',
on_delete=models.PROTECT,
blank=True,
null=True
)
mac_address = MACAddressField(integer=False, unique=True)
machine = models.ForeignKey('Machine', on_delete=models.CASCADE)
type = models.ForeignKey('MachineType', on_delete=models.PROTECT)
@ -267,12 +340,14 @@ class Interface(models.Model):
user = self.machine.user
return machine.active and user.has_access()
@cached_property
def ipv6_object(self):
""" Renvoie un objet type ipv6 à partir du prefix associé à l'iptype parent"""
""" Renvoie un objet type ipv6 à partir du prefix associé à
l'iptype parent"""
if self.type.ip_type.prefix_v6:
return EUI(self.mac_address).ipv6(IPNetwork(self.type.ip_type.prefix_v6).network)
return EUI(self.mac_address).ipv6(
IPNetwork(self.type.ip_type.prefix_v6).network
)
else:
return None
@ -286,7 +361,8 @@ class Interface(models.Model):
return str(EUI(self.mac_address, dialect=mac_bare)).lower()
def filter_macaddress(self):
""" Tente un formatage mac_bare, si échoue, lève une erreur de validation"""
""" Tente un formatage mac_bare, si échoue, lève une erreur de
validation"""
try:
self.mac_address = str(EUI(self.mac_address))
except:
@ -307,7 +383,8 @@ class Interface(models.Model):
if free_ips:
self.ipv4 = free_ips[0]
else:
raise ValidationError("Il n'y a plus d'ip disponibles dans le slash")
raise ValidationError("Il n'y a plus d'ip disponibles\
dans le slash")
return
def unassign_ipv4(self):
@ -323,7 +400,8 @@ class Interface(models.Model):
self.filter_macaddress()
# On verifie la cohérence en forçant l'extension par la méthode
if self.type.ip_type != self.ipv4.ip_type:
raise ValidationError("L'ipv4 et le type de la machine ne correspondent pas")
raise ValidationError("L'ipv4 et le type de la machine ne\
correspondent pas")
super(Interface, self).save(*args, **kwargs)
def __str__(self):
@ -342,18 +420,34 @@ class Interface(models.Model):
def may_have_port_open(self):
""" True si l'interface a une ip et une ip publique.
Permet de ne pas exporter des ouvertures sur des ip privées (useless)"""
Permet de ne pas exporter des ouvertures sur des ip privées
(useless)"""
return self.ipv4 and not self.has_private_ip()
class Domain(models.Model):
""" Objet domain. Enregistrement A et CNAME en même temps : permet de stocker les
alias et les nom de machines, suivant si interface_parent ou cname sont remplis"""
""" Objet domain. Enregistrement A et CNAME en même temps : permet de
stocker les alias et les nom de machines, suivant si interface_parent
ou cname sont remplis"""
PRETTY_NAME = "Domaine dns"
interface_parent = models.OneToOneField('Interface', on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(help_text="Obligatoire et unique, ne doit pas comporter de points", max_length=255)
interface_parent = models.OneToOneField(
'Interface',
on_delete=models.CASCADE,
blank=True,
null=True
)
name = models.CharField(
help_text="Obligatoire et unique, ne doit pas comporter de points",
max_length=255
)
extension = models.ForeignKey('Extension', on_delete=models.PROTECT)
cname = models.ForeignKey('self', null=True, blank=True, related_name='related_domain')
cname = models.ForeignKey(
'self',
null=True,
blank=True,
related_name='related_domain'
)
class Meta:
unique_together = (("name", "extension"),)
@ -372,21 +466,26 @@ class Domain(models.Model):
""" Validation :
- l'objet est bien soit A soit CNAME
- le cname est pas pointé sur lui-même
- le nom contient bien les caractères autorisés par la norme dns et moins de 63 caractères au total
- le nom contient bien les caractères autorisés par la norme
dns et moins de 63 caractères au total
- le couple nom/extension est bien unique"""
if self.get_extension():
self.extension = self.get_extension()
""" 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-]+(?<!-)$", re.IGNORECASE)
HOSTNAME_LABEL_PATTERN = re.compile(
"(?!-)[A-Z\d-]+(?<!-)$",
re.IGNORECASE
)
dns = self.name.lower()
if len(dns) > 63:
raise ValidationError("Le nom de domaine %s est trop long (maximum de 63 caractères)." % dns)
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)
raise ValidationError("Ce nom de domaine %s contient des\
carractères interdits." % dns)
self.validate_unique()
super(Domain, self).clean()
@ -397,7 +496,8 @@ class Domain(models.Model):
return str(self.name) + " IN CNAME " + str(self.cname) + "."
def save(self, *args, **kwargs):
""" Empèche le save sans extension valide. Force à avoir appellé clean avant"""
""" Empèche le save sans extension valide.
Force à avoir appellé clean avant"""
if not self.get_extension():
raise ValidationError("Extension invalide")
self.full_clean()
@ -406,6 +506,7 @@ class Domain(models.Model):
def __str__(self):
return str(self.name) + str(self.extension)
class IpList(models.Model):
PRETTY_NAME = "Addresses ipv4"
@ -414,13 +515,15 @@ class IpList(models.Model):
@cached_property
def need_infra(self):
""" Permet de savoir si un user basique peut assigner cette ip ou non"""
""" Permet de savoir si un user basique peut assigner cette ip ou
non"""
return self.ip_type.need_infra
def clean(self):
""" Erreur si l'ip_type est incorrect"""
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!")
raise ValidationError("L'ipv4 et le range de l'iptype ne\
correspondent pas!")
return
def save(self, *args, **kwargs):
@ -430,24 +533,36 @@ class IpList(models.Model):
def __str__(self):
return self.ipv4
class Service(models.Model):
""" Definition d'un service (dhcp, dns, etc)"""
service_type = models.CharField(max_length=255, blank=True, unique=True)
min_time_regen = models.DurationField(default=timedelta(minutes=1), help_text="Temps minimal avant nouvelle génération du service")
regular_time_regen = models.DurationField(default=timedelta(hours=1), help_text="Temps maximal avant nouvelle génération du service")
min_time_regen = models.DurationField(
default=timedelta(minutes=1),
help_text="Temps minimal avant nouvelle génération du service"
)
regular_time_regen = models.DurationField(
default=timedelta(hours=1),
help_text="Temps maximal avant nouvelle génération du service"
)
servers = models.ManyToManyField('Interface', through='Service_link')
def ask_regen(self):
""" Marque à True la demande de régénération pour un service x """
Service_link.objects.filter(service=self).exclude(asked_regen=True).update(asked_regen=True)
Service_link.objects.filter(service=self).exclude(asked_regen=True)\
.update(asked_regen=True)
return
def process_link(self, servers):
""" 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)):
""" 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)
):
link = Service_link(service=self, server=serv)
link.save()
Service_link.objects.filter(service=self).exclude(server__in=servers).delete()
Service_link.objects.filter(service=self).exclude(server__in=servers)\
.delete()
return
def save(self, *args, **kwargs):
@ -456,13 +571,16 @@ class Service(models.Model):
def __str__(self):
return str(self.service_type)
def regen(service):
""" Fonction externe pour régérération d'un service, prend un objet service en arg"""
""" Fonction externe pour régérération d'un service, prend un objet service
en arg"""
obj = Service.objects.filter(service_type=service)
if obj:
obj[0].ask_regen()
return
class Service_link(models.Model):
""" Definition du lien entre serveurs et services"""
service = models.ForeignKey('Service', on_delete=models.CASCADE)
@ -477,11 +595,16 @@ class Service_link(models.Model):
self.save()
def need_regen(self):
""" Décide si le temps minimal écoulé est suffisant pour provoquer une régénération de service"""
if (self.asked_regen and (self.last_regen + self.service.min_time_regen) < timezone.now()) or (self.last_regen + self.service.regular_time_regen) < timezone.now():
return True
else:
return False
""" Décide si le temps minimal écoulé est suffisant pour provoquer une
régénération de service"""
return bool(
(self.asked_regen and (
self.last_regen + self.service.min_time_regen
) < timezone.now()
) or (
self.last_regen + self.service.regular_time_regen
) < timezone.now()
)
def __str__(self):
return str(self.server) + " " + str(self.service)
@ -489,22 +612,41 @@ class Service_link(models.Model):
class OuverturePortList(models.Model):
"""Liste des ports ouverts sur une interface."""
name = models.CharField(help_text="Nom de la configuration des ports.", max_length=255)
name = models.CharField(
help_text="Nom de la configuration des ports.",
max_length=255
)
def __str__(self):
return self.name
def tcp_ports_in(self):
return self.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.IN)
"""Renvoie la liste des ports ouverts en TCP IN pour ce profil"""
return self.ouvertureport_set.filter(
protocole=OuverturePort.TCP,
io=OuverturePort.IN
)
def udp_ports_in(self):
return self.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.IN)
"""Renvoie la liste des ports ouverts en UDP IN pour ce profil"""
return self.ouvertureport_set.filter(
protocole=OuverturePort.UDP,
io=OuverturePort.IN
)
def tcp_ports_out(self):
return self.ouvertureport_set.filter(protocole=OuverturePort.TCP, io=OuverturePort.OUT)
"""Renvoie la liste des ports ouverts en TCP OUT pour ce profil"""
return self.ouvertureport_set.filter(
protocole=OuverturePort.TCP,
io=OuverturePort.OUT
)
def udp_ports_out(self):
return self.ouvertureport_set.filter(protocole=OuverturePort.UDP, io=OuverturePort.OUT)
"""Renvoie la liste des ports ouverts en UDP OUT pour ce profil"""
return self.ouvertureport_set.filter(
protocole=OuverturePort.UDP,
io=OuverturePort.OUT
)
class OuverturePort(models.Model):
@ -522,7 +664,10 @@ class OuverturePort(models.Model):
OUT = 'O'
begin = models.PositiveIntegerField(validators=[MaxValueValidator(65535)])
end = models.PositiveIntegerField(validators=[MaxValueValidator(65535)])
port_list = models.ForeignKey('OuverturePortList', on_delete=models.CASCADE)
port_list = models.ForeignKey(
'OuverturePortList',
on_delete=models.CASCADE
)
protocole = models.CharField(
max_length=1,
choices=(
@ -546,88 +691,123 @@ class OuverturePort(models.Model):
return '-'.join([str(self.begin), str(self.end)])
def show_port(self):
"""Formatage plus joli, alias pour str"""
return str(self)
@receiver(post_save, sender=Machine)
def machine_post_save(sender, **kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification
d'une machine"""
user = kwargs['instance'].user
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
regen('dhcp')
regen('mac_ip_list')
@receiver(post_delete, sender=Machine)
def machine_post_delete(sender, **kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression
d'une machine"""
machine = kwargs['instance']
user = machine.user
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
regen('dhcp')
regen('mac_ip_list')
@receiver(post_save, sender=Interface)
def interface_post_save(sender, **kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification
d'une interface"""
interface = kwargs['instance']
user = interface.machine.user
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
if not interface.may_have_port_open() and interface.port_lists.all():
interface.port_lists.clear()
# Regen services
regen('dhcp')
regen('mac_ip_list')
@receiver(post_delete, sender=Interface)
def interface_post_delete(sender, **kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la suppression
d'une interface"""
interface = kwargs['instance']
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):
"""Generation des objets ip après modification d'un range ip"""
iptype = kwargs['instance']
iptype.gen_ip_range()
@receiver(post_save, sender=MachineType)
def machine_post_save(sender, **kwargs):
"""Mise à jour des interfaces lorsque changement d'attribution
d'une machinetype (changement iptype parent)"""
machinetype = kwargs['instance']
for interface in machinetype.all_interfaces():
interface.update_type()
@receiver(post_save, sender=Domain)
def domain_post_save(sender, **kwargs):
"""Regeneration dns après modification d'un domain object"""
regen('dns')
@receiver(post_delete, sender=Domain)
def domain_post_delete(sender, **kwargs):
"""Regeneration dns après suppression d'un domain object"""
regen('dns')
@receiver(post_save, sender=Extension)
def extension_post_save(sender, **kwargs):
"""Regeneration dns après modification d'une extension"""
regen('dns')
@receiver(post_delete, sender=Extension)
def extension_post_selete(sender, **kwargs):
"""Regeneration dns après suppression d'une extension"""
regen('dns')
@receiver(post_save, sender=Mx)
def mx_post_save(sender, **kwargs):
"""Regeneration dns après modification d'un MX"""
regen('dns')
@receiver(post_delete, sender=Mx)
def mx_post_delete(sender, **kwargs):
"""Regeneration dns après suppresson d'un MX"""
regen('dns')
@receiver(post_save, sender=Ns)
def ns_post_save(sender, **kwargs):
"""Regeneration dns après modification d'un NS"""
regen('dns')
@receiver(post_delete, sender=Ns)
def ns_post_delete(sender, **kwargs):
"""Regeneration dns après modification d'un NS"""
regen('dns')
@receiver(post_save, sender=Text)
def text_post_save(sender, **kwargs):
"""Regeneration dns après modification d'un TXT"""
regen('dns')
@receiver(post_delete, sender=Text)
def text_post_delete(sender, **kwargs):
"""Regeneration dns après modification d'un TX"""
regen('dns')