views:

328

answers:

3

I am writing a Django application that stores IP addresses with optional routing information. One of the fields for the IP model I have created is nexthop (for next-hop routes), which will usually be empty. Originally we intended to use MySQL, but now project requirements have changed to use PostgreSQL.

Here is a stripped down version of my model:

class IP(models.Model):
    address = models.IPAddressField()
    netmask = models.IPAddressField(default='255.255.255.255')
    nexthop = models.IPAddressField(null=True, blank=True, default=None)
    active  = models.BooleanField('is active?', default=1)

So, with MySQL I did not have a problem leaving the nexthop field empty. However, now that I switched the development environment to Postgres, we've run into a known issue in Django 1.1.1 in which a blank IP address raises a DataError

invalid input syntax for type inet: ""
LINE 1: ...-14 13:07:29', 1, E'1.2.3.4', E'255.255.255.255', E'', true)
                                                             ^

As you can see, it bombs because it is trying to insert an empty string when the column will only accept a NULL.

I have a very real need to be able to keep this field empty, because if an IP doesn't have a next-hop, then its behavior changes.

Short of hacking the Django code manually, which is my ultimate last resort, I have also thought of defaulting next-hop to 255.255.255.255 and wrapping some business logic around that (i.e. If next-hop is 255.255.255.255, treat as normal route), but that just feels like a hack.

I would like to know if there are any suggestions on a better way to do this that would not require hacking Django or writing hacky logic, or if there is a completely different approach altogether that can satisfy my requirement.

Thanks in advance!

Edit: Interim Solution

For the time-being (as an interstitial fix) I decided to go with the sentinel value for the next-hop:

In the model:

IP_NEXTHOP_SENTINEL = '255.255.255.255'
class IP(models.Model):
    nexthop = models.IPAddressField(
        null=True,
        blank=True,
        default=IP_NEXTHOP_SENTINEL,
        help_text='Use %s to indicate no next-hop' % IP_NEXTHOP_SENTINEL
    )

    def save(self, *args, **kwargs):
        ## hack for Django #5622 (http://code.djangoproject.com/ticket/5622)
        if self.nexthop and self.nexthop == IP_NEXTHOP_SENTINEL:
            self.nexthop = None

Overview:

Creation of IP objects outside of the admin portal works as intended, which is why I kept the null=True argument on the nexthop field. The only place where 255.255.255.255 would ever be set as the next-hop would be through the admin portal, so I decided that overloading save() to always replace the sentinel with None would give me the final result I desire and it doesn't really feel too much like a hack.

Thanks for your input on this!

+2  A: 

If you can convince the devs to accept one of the patches, I'd say just run a patched copy of Django until the patched version lands. If not, then it might be less headache to just use a sentinel value, as you suggested, even though it is a hack. You might also just use a regular CharField instead of an IPAddressField, but then you get stuck having to maintain validation logic on your own.

Hank Gay
Yeah, using a `CharField` would be more of a hack than just using a sentinel value. Using a patched version of Django would be a higher cost due to maintaining a branch in our package management system. Now, to make friends with a Django dev? That has appeal!
jathanism
Getting the devs to accept one of the patches does seem to be the ideal fix, and then you only have to handle a patched version of Django for a little while. As for package management, `virtualenv` and `pip` seem to be a better fit for most web applications, so you can use the specific version of Django you need without impacting other apps.
Hank Gay
+1  A: 

Have you tried just using blank=True for nexthop, rather than both blank=True and null=True? As noted in the Django docs:

Only use null=True for non-string fields such as integers, booleans and dates.

mipadi
(Whoops, deleted my comment.) Yes, I have tried everything pretty much, however I have to pass `blank=True` so the admin portal does not consider this a required field. I must use `null=True` because the PostgreSQL `inet` column requires the field to be `NULL` if it is going to be empty.
jathanism
Ah yes, I see. Sorry -- I misunderstood the problem before.
mipadi
No worries. I appreciate you taking the time to post a response. :)
jathanism
A: 

And what about using empty string as sentinel, instead of 255.255.255.255 ?

As admin back-end stores empty field as empty string (when blank=true), this solution has the advantage to be transparent for the user and doesn't force him to use a faked value...

Cédric