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...
0

There are 0 best solutions below