Browse Source

Prepare for 0.2. Migrate from IPy to netaddr

James Oakley 12 years ago
parent
commit
0f5f513f1b
10 changed files with 155 additions and 69 deletions
  1. 3 0
      .gitignore
  2. 1 0
      AUTHORS
  3. 41 13
      README.rst
  4. 17 5
      netfields/fields.py
  5. 29 8
      netfields/forms.py
  6. 0 2
      netfields/managers.py
  7. 60 31
      netfields/tests.py
  8. 0 1
      requirements.txt
  9. 4 5
      setup.py
  10. 0 4
      tox.ini

+ 3 - 0
.gitignore

@@ -4,3 +4,6 @@ MANIFEST
 build/
 dist/
 *.egg-info/
+.eric?project/
+.issues/
+*.e4p

+ 1 - 0
AUTHORS

@@ -4,3 +4,4 @@ Visualised (http://metanav.uninett.no).
  * Thomas Adamcik
  * Magnus Eide
  * Ewoud Kohl van Wijngaarden
+ * James Oakley

+ 41 - 13
README.rst

@@ -9,22 +9,28 @@ and uses an inefficient ``HOST()`` cast in all lookups. As of 1.4 you can use
 ``GenericIPAddressField`` for IPv6, but the casting problem remains.
 
 In addition to the basic ``IPAddressField`` replacement a ``CIDR`` and
-``MACADDR`` field have been added. This library also provides a manager that
+a ``MACADDR`` field have been added. This library also provides a manager that
 allows for advanced IP based lookup directly in the ORM.
 
+In Python, the values of these fields are represented as types from the
+``netaddr_`` module.
+
+.. _netaddr: http://pythonhosted.org/netaddr/
+
 Dependencies
 ------------
 
-Current version of code is targeting Django 1.3-1.4 support, as this relies heavily
-on ORM internals supporting multiple versions is especially tricky. ``IPy`` is
-used for the same reasons. ``ipaddr`` is being considered, but the conversion
-hinges on the related projects conversion to ``ipaddr``.
+Current version of code is targeting Django 1.3-1.4 support, as this relies
+heavily on ORM internals supporting multiple versions is especially tricky. The
+``netaddr`` module is used for the same reasons.
 
 Getting started
 ---------------
 
-Make sure ``netfields`` is in your ``PYTHONPATH``, then simply use the
-following::
+Make sure ``netfields`` is in your ``PYTHONPATH``.
+
+``InetAddressField`` will store values in PostgreSQL as type ``INET``. In
+Python, the value will be represented as a ``netaddr.IPAddress`` object.
 
  from netfields import InetAddressField, NetManager
 
@@ -34,12 +40,34 @@ following::
 
      objects = NetManager()
 
-The package also provides ``CidrAddressField`` and a ``MACAddressField``.
-``NetManager`` is required for the extra lookups to be available. Lookups for
-``INET`` and ``CIDR`` database types will be handled differently than when
-running vanilla Django.  All lookups are case-insensitive and text based
-lookups are avoided whenever possible. In addition to Django's default lookup
-types the following have been added.
+``CidrAddressField`` will store values in PostgreSQL as type ``CIDR``. In
+Python, the value will be represented as a ``netaddr.IPNetwork`` object.
+
+ from netfields import CidrAddressField, NetManager
+
+ class Example(models.Model):
+     inet = CidrAddressField()
+     # ...
+
+     objects = NetManager()
+
+``MACAddressField`` will store values in PostgreSQL as type ``MACADDR``. In
+Python, the value will be represented as a ``netaddr.EUI`` object. Note that
+the default text representation of EUI objects is not the same as that of the
+``netaddr`` module. It is represented in a format that is more commonly used
+in network utilities and by network administrators (``00:11:22:aa:bb:cc``).
+
+ from netfields import CidrAddressField, NetManager
+
+ class Example(models.Model):
+     inet = CidrAddressField()
+     # ...
+
+For ``InetAddressField`` and ``CidrAddressField``, ``NetManager`` is required
+for the extra lookups to be available. Lookups for ``INET`` and ``CIDR``
+database types will be handled differently than when running vanilla Django.
+All lookups are case-insensitive and text based lookups are avoided whenever
+possible. In addition to Django's default lookup types the following have been added.
 
 * ``__net_contained``
 * ``__net_contained_or_equal``

+ 17 - 5
netfields/fields.py

@@ -1,10 +1,9 @@
-from IPy import IP
-from netaddr import EUI
+from netaddr import IPAddress, IPNetwork, EUI
 
 from django.db import models
 
 from netfields.managers import NET_OPERATORS, NET_TEXT_OPERATORS
-from netfields.forms import NetAddressFormField, MACAddressFormField
+from netfields.forms import InetAddressFormField, CidrAddressFormField, MACAddressFormField
 from netfields.mac import mac_unix_common
 
 
@@ -19,7 +18,7 @@ class _NetAddressField(models.Field):
         if not value:
             return value
 
-        return IP(value)
+        return self.python_type()(value)
 
     def get_prep_lookup(self, lookup_type, value):
         if not value:
@@ -51,11 +50,12 @@ class _NetAddressField(models.Field):
             lookup_type, value, connection=connection, prepared=prepared)
 
     def formfield(self, **kwargs):
-        defaults = {'form_class': NetAddressFormField}
+        defaults = {'form_class': self.form_class()}
         defaults.update(kwargs)
         return super(_NetAddressField, self).formfield(**defaults)
 
 
+
 class InetAddressField(_NetAddressField):
     description = "PostgreSQL INET field"
     max_length = 39
@@ -64,6 +64,12 @@ class InetAddressField(_NetAddressField):
     def db_type(self, connection):
         return 'inet'
 
+    def python_type(self):
+        return IPAddress
+
+    def form_class(self):
+        return InetAddressFormField
+
 
 class CidrAddressField(_NetAddressField):
     description = "PostgreSQL CIDR field"
@@ -73,6 +79,12 @@ class CidrAddressField(_NetAddressField):
     def db_type(self, connection):
         return 'cidr'
 
+    def python_type(self):
+        return IPNetwork
+
+    def form_class(self):
+        return CidrAddressFormField
+
 
 class MACAddressField(models.Field):
     description = "PostgreSQL MACADDR field"

+ 29 - 8
netfields/forms.py

@@ -1,5 +1,4 @@
-from IPy import IP
-from netaddr import EUI, AddrFormatError
+from netaddr import IPAddress, IPNetwork, EUI, AddrFormatError
 
 from django import forms
 from django.utils.encoding import force_unicode
@@ -22,25 +21,47 @@ class NetInput(forms.Widget):
         return mark_safe(u'<input%s />' % forms.util.flatatt(final_attrs))
 
 
-class NetAddressFormField(forms.Field):
+class InetAddressFormField(forms.Field):
     widget = NetInput
     default_error_messages = {
         'invalid': u'Enter a valid IP Address.',
     }
 
     def __init__(self, *args, **kwargs):
-        super(NetAddressFormField, self).__init__(*args, **kwargs)
+        super(InetAddressFormField, self).__init__(*args, **kwargs)
 
     def to_python(self, value):
         if not value:
             return None
 
-        if isinstance(value, IP):
+        if isinstance(value, IPAddress):
             return value
 
         try:
-            return IP(value)
-        except ValueError, e:
+            return IPAddress(value)
+        except (AddrFormatError, TypeError), e:
+            raise ValidationError(str(e))
+
+
+class CidrAddressFormField(forms.Field):
+    widget = NetInput
+    default_error_messages = {
+        'invalid': u'Enter a valid CIDR Address.',
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(CidrAddressFormField, self).__init__(*args, **kwargs)
+
+    def to_python(self, value):
+        if not value:
+            return None
+
+        if isinstance(value, IPNetwork):
+            return value
+
+        try:
+            return IPNetwork(value)
+        except (AddrFormatError, TypeError), e:
             raise ValidationError(str(e))
 
 
@@ -61,5 +82,5 @@ class MACAddressFormField(forms.Field):
 
         try:
             return EUI(value, dialect=mac_unix_common)
-        except AddrFormatError:
+        except (AddrFormatError, TypeError):
             raise ValidationError(self.error_messages['invalid'])

+ 0 - 2
netfields/managers.py

@@ -1,5 +1,3 @@
-from IPy import IP
-
 from django.db import models, connection
 from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper
 from django.db.models import sql, query

+ 60 - 31
netfields/tests.py

@@ -1,5 +1,4 @@
-from IPy import IP
-from netaddr import EUI
+from netaddr import IPAddress, IPNetwork, EUI, AddrFormatError
 
 from django.db import IntegrityError
 from django.forms import ModelForm
@@ -69,15 +68,11 @@ class BaseSqlTestCase(object):
 
 
 class BaseInetTestCase(BaseSqlTestCase):
-    value1 = '10.0.0.1'
-    value2 = '10.0.0.2'
-    value3 = '10.0.0.10'
-
     def test_save_object(self):
-        self.model(field=IP(self.value1)).save()
+        self.model(field=self.value1).save()
 
     def test_init_with_text_fails(self):
-        self.assertRaises(ValueError, self.model, field='abc')
+        self.assertRaises(AddrFormatError, self.model, field='abc')
 
     def test_iexact_lookup(self):
         self.assertSqlEquals(self.qs.filter(field__iexact=self.value1),
@@ -95,24 +90,12 @@ class BaseInetTestCase(BaseSqlTestCase):
     def test_day_lookup_fails(self):
         self.assertSqlRaises(self.qs.filter(field__day=1), ValueError)
 
-    def test_net_contains_lookup(self):
-        self.assertSqlEquals(self.qs.filter(field__net_contains='10.0.0.1'),
-            self.select + 'WHERE "table"."field" >> %s ')
-
-    def test_net_contains_or_equals(self):
-        self.assertSqlEquals(self.qs.filter(field__net_contains_or_equals='10.0.0.1'),
-            self.select + 'WHERE "table"."field" >>= %s ')
-
-    def test_net_contained(self):
-        self.assertSqlEquals(self.qs.filter(field__net_contained='10.0.0.1'),
-            self.select + 'WHERE "table"."field" << %s ')
-
-    def test_net_contained_or_equals(self):
-        self.assertSqlEquals(self.qs.filter(field__net_contained_or_equal='10.0.0.1'),
-            self.select + 'WHERE "table"."field" <<= %s ')
-
 
 class BaseInetFieldTestCase(BaseInetTestCase):
+    value1 = '10.0.0.1'
+    value2 = '10.0.0.2'
+    value3 = '10.0.0.10'
+
     def test_startswith_lookup(self):
         self.assertSqlEquals(self.qs.filter(field__startswith='10.'),
             self.select + 'WHERE HOST("table"."field") ILIKE %s ')
@@ -139,6 +122,10 @@ class BaseInetFieldTestCase(BaseInetTestCase):
 
 
 class BaseCidrFieldTestCase(BaseInetTestCase):
+    value1 = '10.0.0.1/32'
+    value2 = '10.0.0.2/24'
+    value3 = '10.0.0.10/16'
+
     def test_startswith_lookup(self):
         self.assertSqlEquals(self.qs.filter(field__startswith='10.'),
             self.select + 'WHERE TEXT("table"."field") ILIKE %s ')
@@ -163,6 +150,23 @@ class BaseCidrFieldTestCase(BaseInetTestCase):
         self.assertSqlEquals(self.qs.filter(field__iregex='10'),
             self.select + 'WHERE TEXT("table"."field") ~* %s ')
 
+    def test_net_contains_lookup(self):
+        self.assertSqlEquals(self.qs.filter(field__net_contains='10.0.0.1'),
+            self.select + 'WHERE "table"."field" >> %s ')
+
+    def test_net_contains_or_equals(self):
+        self.assertSqlEquals(self.qs.filter(field__net_contains_or_equals='10.0.0.1'),
+            self.select + 'WHERE "table"."field" >>= %s ')
+
+    def test_net_contained(self):
+        self.assertSqlEquals(self.qs.filter(field__net_contained='10.0.0.1'),
+            self.select + 'WHERE "table"."field" << %s ')
+
+    def test_net_contained_or_equals(self):
+        self.assertSqlEquals(self.qs.filter(field__net_contained_or_equal='10.0.0.1'),
+            self.select + 'WHERE "table"."field" <<= %s ')
+
+
 
 class TestInetField(BaseInetFieldTestCase, TestCase):
     def setUp(self):
@@ -228,28 +232,53 @@ class TestCidrFieldNullable(BaseCidrFieldTestCase, TestCase):
         self.model().save()
 
 
-class InetTestModelForm(ModelForm):
+class InetAddressTestModelForm(ModelForm):
     class Meta:
         model = InetTestModel
 
 
+class TestInetAddressFormField(TestCase):
+    def test_form_ipv4_valid(self):
+        form = InetAddressTestModelForm({'field': '10.0.0.1'})
+        self.assertTrue(form.is_valid())
+        self.assertEqual(form.cleaned_data['field'], IPAddress('10.0.0.1'))
+
+    def test_form_ipv4_invalid(self):
+        form = InetAddressTestModelForm({'field': '10.0.0.1.2'})
+        self.assertFalse(form.is_valid())
+
+    def test_form_ipv6(self):
+        form = InetAddressTestModelForm({'field': '2001:0:1::2'})
+        self.assertTrue(form.is_valid())
+        self.assertEqual(form.cleaned_data['field'], IPAddress('2001:0:1::2'))
+
+    def test_form_ipv6_invalid(self):
+        form = InetAddressTestModelForm({'field': '2001:0::1::2'})
+        self.assertFalse(form.is_valid())
+
+
+class CidrAddressTestModelForm(ModelForm):
+    class Meta:
+        model = CidrTestModel
+
+
 class TestNetAddressFormField(TestCase):
     def test_form_ipv4_valid(self):
-        form = InetTestModelForm({'field': '10.0.0.1'})
+        form = CidrAddressTestModelForm({'field': '10.0.0.1/24'})
         self.assertTrue(form.is_valid())
-        self.assertEqual(form.cleaned_data['field'], IP('10.0.0.1'))
+        self.assertEqual(form.cleaned_data['field'], IPNetwork('10.0.0.1/24'))
 
     def test_form_ipv4_invalid(self):
-        form = InetTestModelForm({'field': '10.0.0.1.2'})
+        form = CidrAddressTestModelForm({'field': '10.0.0.1.2/32'})
         self.assertFalse(form.is_valid())
 
     def test_form_ipv6(self):
-        form = InetTestModelForm({'field': '2001:0:1::2'})
+        form = CidrAddressTestModelForm({'field': '2001:0:1::2/64'})
         self.assertTrue(form.is_valid())
-        self.assertEqual(form.cleaned_data['field'], IP('2001:0:1::2'))
+        self.assertEqual(form.cleaned_data['field'], IPNetwork('2001:0:1::2/64'))
 
     def test_form_ipv6_invalid(self):
-        form = InetTestModelForm({'field': '2001:0::1::2'})
+        form = CidrAddressTestModelForm({'field': '2001:0::1::2/128'})
         self.assertFalse(form.is_valid())
 
 

+ 0 - 1
requirements.txt

@@ -1,4 +1,3 @@
-IPy
 django>=1.3
 netaddr
 psycopg2

+ 4 - 5
setup.py

@@ -12,20 +12,19 @@ def get_long_description():
 
 setup(
     name='django-netfields',
-    version='0.1',
+    version='0.2',
     license='BSD',
     description='Django PostgreSQL netfields implementation',
     long_description=get_long_description(),
-    url='https://github.com/adamcik/django-postgresql-netfields',
+    url='https://github.com/jimfunk/django-postgresql-netfields',
 
-    author=u'Thomas Admacik',
-    author_email='adamcik@samfundet.no',
+    author=u'James Oakley',
+    author_email='jfunk@funktronics.ca',
 
     packages=find_packages(),
     include_package_data=True,
     zip_safe=False,
     install_requires=[
-        'IPy',
         'netaddr',
         'django>=1.3',
     ],

+ 0 - 4
tox.ini

@@ -14,7 +14,6 @@ commands=
 [testenv:py26-django13]
 basepython=python2.6
 deps=
-    IPy
     django==1.3
     netaddr
     psycopg2==2.4.1
@@ -22,7 +21,6 @@ deps=
 [testenv:py27-django13]
 basepython=python2.7
 deps=
-    IPy
     django==1.3
     netaddr
     psycopg2==2.4.1
@@ -30,7 +28,6 @@ deps=
 [testenv:py26-django14]
 basepython=python2.6
 deps=
-    IPy
     django==1.4
     netaddr
     psycopg2
@@ -38,7 +35,6 @@ deps=
 [testenv:py27-django14]
 basepython=python2.7
 deps=
-    IPy
     django==1.4
     netaddr
     psycopg2