I have three models (URL, Hostname, IPAddress). If I create a URL the overridden save() method of that class finds the hostname and does get_or_create() for Hostname. The save() method for Hostname does a similar thing for IPAddress etc.
- Am I doing this the hard way?
Should I be doing some kind of inheritance stuff with this instead? I don't know if inheritance is the way to go because a URL can have one hostname but also have many IP addresses. Inheritance doesn't seem suited for that kind of situation. I have to admit I haven't tried using inheritances yet but if it's a possible solution to this I'm willing to rewrite the entire thing.
I cannot seem to get the ValidationError that validate_ip() throws to trickle all the way up to when I attempt to add a URL that has an IP address that doesn't pass the validator. It throws the exception in django admin but not in the nice way of putting the error above the URL entry field. Instead of it gives a 500 error page.
EDIT: Video capture of what I mean --> https://i.imgur.com/8W0wiWT.mp4
...snip...
def validate_ip(ip_addr):
"""Check ip_addr:
- belongs to an ASN we manage
- not whitelisted
"""
asn, _, _ = lookup_asn_info(ip_addr)
# pylint: disable=no-member
con_objs = Constituency.objects.filter(asns__contains=[asn])
if not con_objs:
raise ValidationError("{} is not ours.".format(ip_addr))
wl_networks = con_objs.values_list("ip_whitelist", flat=True).first()
for wl_network in wl_networks:
if wl_network.overlaps(ip_addr):
raise ValidationError("{} is whitelisted".format(ip_addr))
...snip...
class URL(models.Model):
"""URL model."""
url = models.CharField(max_length=2000, unique=True, verbose_name="URL")
hostname = models.ForeignKey("Hostname", blank=True, on_delete=models.CASCADE)
ipaddresses = models.ManyToManyField(
"IPAddress", blank=True, verbose_name="IP Addresses"
)
def save(self, *args, **kwargs):
"""URL save()"""
# Hostname
if not hasattr(self, "hostname"):
hostname = urlparse(self.url).hostname
# pylint: disable=no-member
self.hostname, _ = Hostname.objects.get_or_create(hostname=hostname)
super().save(*args, **kwargs)
ip_objs = self.hostname.ipaddresses.all()
for ip_obj in ip_objs:
ip_obj.urls.add(self)
self.ipaddresses.add(ip_obj) # pylint: disable=no-member
self.hostname.urls.add(self)
# pylint: disable=too-few-public-methods
class Meta:
"""URL Meta."""
verbose_name = "URL"
verbose_name_plural = "URLs"
def __str__(self):
return "{}".format(self.url)
def all_ipaddresses(self):
"""Return all IP Addresses for this ManyToManyField."""
# pylint: disable=no-member
return ", ".join([str(i.ipaddress) for i in self.ipaddresses.all()])
class Hostname(models.Model):
"""Hostname model."""
hostname = models.CharField(max_length=255, unique=True)
ipaddresses = models.ManyToManyField(
"IPAddress", blank=True, verbose_name="IP Addresses"
)
urls = models.ManyToManyField(
"URL", blank=True, related_name="+", verbose_name="URLs"
)
def save(self, *args, **kwargs):
"""Hostname save()"""
# TODO: Check whitelist
ip_objs = []
for ip_addr in resolve_hostname(self.hostname):
ip_obj, _ = IPAddress.objects.get_or_create(ipaddress=ip_addr)
ip_objs.append(ip_obj)
super().save(*args, **kwargs)
self.ipaddresses.set(ip_objs) # pylint: disable=no-member
for ip_obj in ip_objs:
ip_obj.hostnames.add(self)
# pylint: disable=too-few-public-methods
class Meta:
"""Hostname Meta."""
verbose_name = "Hostname"
verbose_name_plural = "Hostnames"
def __str__(self):
return "{}".format(self.hostname)
def all_ipaddresses(self):
"""Return all IP Addresses for this ManyToManyField."""
# pylint: disable=no-member
return ", ".join([str(i.ipaddress) for i in self.ipaddresses.all()])
def all_urls(self):
"""Return all URLs for this ManyToManyField."""
# pylint: disable=no-member
return ", ".join([i.url for i in self.urls.all()])
class IPAddress(models.Model):
"""IPAddress model."""
ipaddress = CidrAddressField(
verbose_name="IP Address", unique=True, validators=[validate_ip]
)
objects = NetManager()
hostnames = models.ManyToManyField("Hostname", blank=True)
urls = models.ManyToManyField("URL", blank=True, verbose_name="URLs")
asn = models.IntegerField(blank=True, verbose_name="AS Number")
as_name = models.CharField(max_length=255, blank=True, verbose_name="AS Name")
as_country = models.CharField(max_length=50, blank=True, verbose_name="AS Country")
def save(self, *args, **kwargs):
"""IPAddress save()"""
# Fetch the ASN data for this IP address
self.asn, self.as_country, self.as_name = lookup_asn_info(self.ipaddress)
self.full_clean()
super().save(*args, **kwargs)
# pylint: disable=too-few-public-methods
class Meta:
"""IPAddress Meta."""
verbose_name = "IP Address"
verbose_name_plural = "IP Addresses"
def __str__(self):
return "{}".format(self.ipaddress)
def all_hostnames(self):
"""Return all Hostnames for this ManyToManyField."""
# pylint: disable=no-member
return ", ".join([str(i.hostname) for i in self.hostnames.all()])
def all_urls(self):
"""Return all URLs for this ManyToManyField."""
# pylint: disable=no-member
return ", ".join([i.url for i in self.urls.all()])
...snip...