django-netfields-0.8/0000755000175000001440000000000013212123563015070 5ustar jfunkusers00000000000000django-netfields-0.8/testsettings.py0000644000175000001440000000064112611625041020203 0ustar jfunkusers00000000000000DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'netfields', } } INSTALLED_APPS = ( 'netfields', 'test', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) SECRET_KEY = "notimportant" django-netfields-0.8/django_netfields.egg-info/0000755000175000001440000000000013212123563022061 5ustar jfunkusers00000000000000django-netfields-0.8/django_netfields.egg-info/PKG-INFO0000644000175000001440000001455113212123563023164 0ustar jfunkusers00000000000000Metadata-Version: 1.1 Name: django-netfields Version: 0.8 Summary: Django PostgreSQL netfields implementation Home-page: https://github.com/jimfunk/django-postgresql-netfields Author: James Oakley Author-email: jfunk@funktronics.ca License: BSD Description-Content-Type: UNKNOWN Description: Django PostgreSQL Netfields =========================== .. image:: https://secure.travis-ci.org/jimfunk/django-postgresql-netfields.png This project is an attempt at making proper PostgreSQL net related fields for Django. In Django pre 1.4 the built in ``IPAddressField`` does not support IPv6 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 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 the IP address fields are represented as types from the ipaddress_ module. In Python 2.x, a backport_ is used. The MAC address field is represented as an EUI type from the netaddr_ module. .. _ipaddress: https://docs.python.org/3/library/ipaddress.html .. _backport: https://pypi.python.org/pypi/ipaddress/ .. _netaddr: http://pythonhosted.org/netaddr/ Dependencies ------------ Current version of code is targeting Django >= 1.8 support, as this relies heavily on ORM internals and supporting multiple versions is especially tricky. Getting started --------------- Make sure ``netfields`` is in your ``PYTHONPATH`` and in ``INSTALLED_APPS``. ``InetAddressField`` will store values in PostgreSQL as type ``INET``. In Python, the value will be represented as an ``ipaddress.ip_interface`` object representing an IP address and netmask/prefix length pair unless the ``store_prefix_length`` argument is set to `False``, in which case the value will be represented as an ``ipaddress.ip_address`` object. from netfields import InetAddressField, NetManager class Example(models.Model): inet = InetAddressField() # ... objects = NetManager() ``CidrAddressField`` will store values in PostgreSQL as type ``CIDR``. In Python, the value will be represented as an ``ipaddress.ip_network`` 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 MACAddressField, NetManager class Example(models.Model): inet = MACAddressField() # ... 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`` is contained within the given network ``__net_contained_or_equal`` is contained within or equal to the given network ``__net_contains`` contains the given address ``__net_contains_or_equals`` contains or is equal to the given address/network ``__net_overlaps`` contains or contained by the given address ``__family`` matches the given address family These correspond with the operators and functions from http://www.postgresql.org/docs/9.4/interactive/functions-net.html ``CidrAddressField`` includes two extra lookups: ``__max_prefixlen`` Maximum value (inclusive) for ``CIDR`` prefix, does not distinguish between IPv4 and IPv6 ``__min_prefixlen`` Minimum value (inclusive) for ``CIDR`` prefix, does not distinguish between IPv4 and IPv6 Related Django bugs ------------------- * 11442_ - Postgresql backend casts inet types to text, breaks IP operations and IPv6 lookups. * 811_ - IPv6 address field support. https://docs.djangoproject.com/en/dev/releases/1.4/#extended-ipv6-support is also relevant .. _11442: http://code.djangoproject.com/ticket/11442 .. _811: http://code.djangoproject.com/ticket/811 Similar projects ---------------- https://bitbucket.org/onelson/django-ipyfield tries to solve some of the same issues as this library. However, instead of supporting just postgres via the proper fields types the ipyfield currently uses a ``VARCHAR(39)`` as a fake unsigned 64 bit number in its implementation. History ------- Main repo was originaly kept https://github.com/adamcik/django-postgresql-netfields Late April 2013 the project was moved to https://github.com/jimfunk/django-postgresql-netfields to pass the torch on to someone who actually uses this code actively :-) Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Utilities django-netfields-0.8/django_netfields.egg-info/requires.txt0000644000175000001440000000003613212123563024460 0ustar jfunkusers00000000000000netaddr django>=1.8 ipaddress django-netfields-0.8/django_netfields.egg-info/not-zip-safe0000644000175000001440000000000112471560012024307 0ustar jfunkusers00000000000000 django-netfields-0.8/django_netfields.egg-info/dependency_links.txt0000644000175000001440000000000113212123563026127 0ustar jfunkusers00000000000000 django-netfields-0.8/django_netfields.egg-info/SOURCES.txt0000644000175000001440000000130613212123563023745 0ustar jfunkusers00000000000000AUTHORS CHANGELOG LICENSE MANIFEST.in README.rst manage.py requirements.txt setup.py testsettings.py django_netfields.egg-info/PKG-INFO django_netfields.egg-info/SOURCES.txt django_netfields.egg-info/dependency_links.txt django_netfields.egg-info/not-zip-safe django_netfields.egg-info/requires.txt django_netfields.egg-info/top_level.txt netfields/__init__.py netfields/apps.py netfields/fields.py netfields/forms.py netfields/lookups.py netfields/mac.py netfields/managers.py netfields/models.py netfields/psycopg2_types.py netfields/rest_framework.py test/__init__.py test/models.py test/tests/__init__.py test/tests/test_form_fields.py test/tests/test_rest_framework_fields.py test/tests/test_sql_fields.pydjango-netfields-0.8/django_netfields.egg-info/top_level.txt0000644000175000001440000000001713212123563024611 0ustar jfunkusers00000000000000netfields test django-netfields-0.8/AUTHORS0000644000175000001440000000030412471552614016146 0ustar jfunkusers00000000000000Written to provide better INET and CIDR handling for Network Administration Visualised (http://metanav.uninett.no). * Thomas Adamcik * Magnus Eide * Ewoud Kohl van Wijngaarden * James Oakley django-netfields-0.8/CHANGELOG0000644000175000001440000000665213172023053016311 0ustar jfunkusers00000000000000- 0.8 * Added support for Django 2.0 - 0.7.2 * Fix issue where prefetch_related returns empty querysets (Jay McEntire) * Improve validation error messages to include the underlying error (Diego Gaustein) * Strip extraneous whitespace around form inputs - 0.7.1 * Fix issue with recent psycopg2 releases (Jonas Genannt) * Add a lookup for exact CIDR prefix length (Joey Wilhelm) * Use proper unicode type when casting ipaddress/ipnetwork objects for queries * Support array aggregation with netfields (orf) * compatibility with template based widgets (Leo Honkanen) - 0.7 * Added support for Django 1.10 * Added __overlaps lookup (Emre Hasegeli) * Fixed __in lookups of empty lists (Simeon J Morgan) - 0.6 * Removed support for Django 1.7 * MACAddressField fixes and cleanups (Andreas Rammhold) * Support for ArrayField from django.contrib.postgres. Note that the types returned in the array will not be the proper types returned by the non-array fields in Django < 1.9.6 due to https://code.djangoproject.com/ticket/25143 - 0.5.3 * Add max_prefixlen and min_prefixlen lookups (Hugo Castilho) * Changed field subclassing to Django 1.8+ format (Antwan86) * Use the ipaddress module instead of py2-ipaddress for Python 2.x * Standard lookups now inherit from base lookups - 0.5.2 * Support for Django 1.9 * Invalid lookups on Django < 1.9 will now raise FieldError or NotImplementedError to match Django 1.9 - 0.5.1 * Fixed form validation error messages to avoid confusion * Updated README to reflect the change to the ipaddress module - 0.5 * Switched InetAddressField and CidrAddressField from netaddr to the ipaddress module found in core Python >= 3.3. For Python < 3 the backported py2-ipaddress module will be used. The API of these objects are mostly compatible with the netaddr API and do not suffer from some subtle issues that netaddr objects have when used in Django forms. Most applications should not require any adjustments as a result of this change - 0.4.1 * Added serializer fields for Django REST Framework. Thanks to Brandon Cazander * Added a store_prefix_length argument to InetAddressField. If set to False an IPAddress will be returned instead of an IPNetwork. If there is a prefix length before conversion, it will be truncated - 0.4 * Return IPNetwork object from InetAddressField instead of IPAddress. This better matches the capabilities of the underlying inet type since it can contain an embedded prefix length/netmask. This change may affect some users * Added __family lookups for filtering by address family, eg: 'filter(ip__family=4)' to select only IPv4 addresses. Thanks to leifurhauks * Removed support for Django < 1.7 - 0.3.1 * Fix CidrAddressField in lookups with single-item lists #39 * Fix validation of CIDR with bits to right of mask #19 - 0.3 * Added support for Django 1.7/1.8 (Antwan86, smclenithan) * Added support for Python 3.x * Removed support for Django 1.4 - 0.2.2 * Support for Django 1.6 (Jay McEntire) - 0.2.1 * Fix net_contained lookups in InetAddressField - 0.2 * Fix IP type casting in form fields * Add South introspection rules * Expand tests for form fields and MAC addresses * Use netaddr instead of IPy for address representation. New requirement of netaddr module. This change affects backwards compatibility * New maintainer James Oakley django-netfields-0.8/requirements.txt0000644000175000001440000000003512471552614020363 0ustar jfunkusers00000000000000django>=1.3 netaddr psycopg2 django-netfields-0.8/PKG-INFO0000644000175000001440000001455113212123563016173 0ustar jfunkusers00000000000000Metadata-Version: 1.1 Name: django-netfields Version: 0.8 Summary: Django PostgreSQL netfields implementation Home-page: https://github.com/jimfunk/django-postgresql-netfields Author: James Oakley Author-email: jfunk@funktronics.ca License: BSD Description-Content-Type: UNKNOWN Description: Django PostgreSQL Netfields =========================== .. image:: https://secure.travis-ci.org/jimfunk/django-postgresql-netfields.png This project is an attempt at making proper PostgreSQL net related fields for Django. In Django pre 1.4 the built in ``IPAddressField`` does not support IPv6 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 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 the IP address fields are represented as types from the ipaddress_ module. In Python 2.x, a backport_ is used. The MAC address field is represented as an EUI type from the netaddr_ module. .. _ipaddress: https://docs.python.org/3/library/ipaddress.html .. _backport: https://pypi.python.org/pypi/ipaddress/ .. _netaddr: http://pythonhosted.org/netaddr/ Dependencies ------------ Current version of code is targeting Django >= 1.8 support, as this relies heavily on ORM internals and supporting multiple versions is especially tricky. Getting started --------------- Make sure ``netfields`` is in your ``PYTHONPATH`` and in ``INSTALLED_APPS``. ``InetAddressField`` will store values in PostgreSQL as type ``INET``. In Python, the value will be represented as an ``ipaddress.ip_interface`` object representing an IP address and netmask/prefix length pair unless the ``store_prefix_length`` argument is set to `False``, in which case the value will be represented as an ``ipaddress.ip_address`` object. from netfields import InetAddressField, NetManager class Example(models.Model): inet = InetAddressField() # ... objects = NetManager() ``CidrAddressField`` will store values in PostgreSQL as type ``CIDR``. In Python, the value will be represented as an ``ipaddress.ip_network`` 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 MACAddressField, NetManager class Example(models.Model): inet = MACAddressField() # ... 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`` is contained within the given network ``__net_contained_or_equal`` is contained within or equal to the given network ``__net_contains`` contains the given address ``__net_contains_or_equals`` contains or is equal to the given address/network ``__net_overlaps`` contains or contained by the given address ``__family`` matches the given address family These correspond with the operators and functions from http://www.postgresql.org/docs/9.4/interactive/functions-net.html ``CidrAddressField`` includes two extra lookups: ``__max_prefixlen`` Maximum value (inclusive) for ``CIDR`` prefix, does not distinguish between IPv4 and IPv6 ``__min_prefixlen`` Minimum value (inclusive) for ``CIDR`` prefix, does not distinguish between IPv4 and IPv6 Related Django bugs ------------------- * 11442_ - Postgresql backend casts inet types to text, breaks IP operations and IPv6 lookups. * 811_ - IPv6 address field support. https://docs.djangoproject.com/en/dev/releases/1.4/#extended-ipv6-support is also relevant .. _11442: http://code.djangoproject.com/ticket/11442 .. _811: http://code.djangoproject.com/ticket/811 Similar projects ---------------- https://bitbucket.org/onelson/django-ipyfield tries to solve some of the same issues as this library. However, instead of supporting just postgres via the proper fields types the ipyfield currently uses a ``VARCHAR(39)`` as a fake unsigned 64 bit number in its implementation. History ------- Main repo was originaly kept https://github.com/adamcik/django-postgresql-netfields Late April 2013 the project was moved to https://github.com/jimfunk/django-postgresql-netfields to pass the torch on to someone who actually uses this code actively :-) Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Utilities django-netfields-0.8/setup.py0000755000175000001440000000225513172022775016621 0ustar jfunkusers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup from setuptools import find_packages import os import sys def get_long_description(): path = os.path.join(os.path.dirname(__file__), 'README.rst') with open(path) as f: return f.read() requirements = [ 'netaddr', 'django>=1.8', ] if sys.version_info.major == 2: requirements.append('ipaddress') setup( name='django-netfields', version='0.8', license='BSD', description='Django PostgreSQL netfields implementation', long_description=get_long_description(), url='https://github.com/jimfunk/django-postgresql-netfields', author=u'James Oakley', author_email='jfunk@funktronics.ca', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requirements, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Utilities', ], ) django-netfields-0.8/MANIFEST.in0000644000175000001440000000021012471552614016630 0ustar jfunkusers00000000000000include AUTHORS include CHANGELOG include LICENSE include README.rst include requirements.txt include manage.py include testsettings.py django-netfields-0.8/README.rst0000644000175000001440000001130413004525762016564 0ustar jfunkusers00000000000000Django PostgreSQL Netfields =========================== .. image:: https://secure.travis-ci.org/jimfunk/django-postgresql-netfields.png This project is an attempt at making proper PostgreSQL net related fields for Django. In Django pre 1.4 the built in ``IPAddressField`` does not support IPv6 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 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 the IP address fields are represented as types from the ipaddress_ module. In Python 2.x, a backport_ is used. The MAC address field is represented as an EUI type from the netaddr_ module. .. _ipaddress: https://docs.python.org/3/library/ipaddress.html .. _backport: https://pypi.python.org/pypi/ipaddress/ .. _netaddr: http://pythonhosted.org/netaddr/ Dependencies ------------ Current version of code is targeting Django >= 1.8 support, as this relies heavily on ORM internals and supporting multiple versions is especially tricky. Getting started --------------- Make sure ``netfields`` is in your ``PYTHONPATH`` and in ``INSTALLED_APPS``. ``InetAddressField`` will store values in PostgreSQL as type ``INET``. In Python, the value will be represented as an ``ipaddress.ip_interface`` object representing an IP address and netmask/prefix length pair unless the ``store_prefix_length`` argument is set to `False``, in which case the value will be represented as an ``ipaddress.ip_address`` object. from netfields import InetAddressField, NetManager class Example(models.Model): inet = InetAddressField() # ... objects = NetManager() ``CidrAddressField`` will store values in PostgreSQL as type ``CIDR``. In Python, the value will be represented as an ``ipaddress.ip_network`` 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 MACAddressField, NetManager class Example(models.Model): inet = MACAddressField() # ... 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`` is contained within the given network ``__net_contained_or_equal`` is contained within or equal to the given network ``__net_contains`` contains the given address ``__net_contains_or_equals`` contains or is equal to the given address/network ``__net_overlaps`` contains or contained by the given address ``__family`` matches the given address family These correspond with the operators and functions from http://www.postgresql.org/docs/9.4/interactive/functions-net.html ``CidrAddressField`` includes two extra lookups: ``__max_prefixlen`` Maximum value (inclusive) for ``CIDR`` prefix, does not distinguish between IPv4 and IPv6 ``__min_prefixlen`` Minimum value (inclusive) for ``CIDR`` prefix, does not distinguish between IPv4 and IPv6 Related Django bugs ------------------- * 11442_ - Postgresql backend casts inet types to text, breaks IP operations and IPv6 lookups. * 811_ - IPv6 address field support. https://docs.djangoproject.com/en/dev/releases/1.4/#extended-ipv6-support is also relevant .. _11442: http://code.djangoproject.com/ticket/11442 .. _811: http://code.djangoproject.com/ticket/811 Similar projects ---------------- https://bitbucket.org/onelson/django-ipyfield tries to solve some of the same issues as this library. However, instead of supporting just postgres via the proper fields types the ipyfield currently uses a ``VARCHAR(39)`` as a fake unsigned 64 bit number in its implementation. History ------- Main repo was originaly kept https://github.com/adamcik/django-postgresql-netfields Late April 2013 the project was moved to https://github.com/jimfunk/django-postgresql-netfields to pass the torch on to someone who actually uses this code actively :-) django-netfields-0.8/test/0000755000175000001440000000000013212123563016047 5ustar jfunkusers00000000000000django-netfields-0.8/test/models.py0000644000175000001440000000376313073312234017715 0ustar jfunkusers00000000000000from django.contrib.postgres.fields import ArrayField from django.db.models import Model, ForeignKey from netfields import InetAddressField, CidrAddressField, MACAddressField, \ NetManager class InetTestModel(Model): field = InetAddressField() objects = NetManager() class Meta: db_table = 'inet' class NullInetTestModel(Model): field = InetAddressField(null=True) objects = NetManager() class Meta: db_table = 'nullinet' class UniqueInetTestModel(Model): field = InetAddressField(unique=True) objects = NetManager() class Meta: db_table = 'uniqueinet' class NoPrefixInetTestModel(Model): field = InetAddressField(store_prefix_length=False) objects = NetManager() class Meta: db_table = 'noprefixinet' class CidrTestModel(Model): field = CidrAddressField() objects = NetManager() class Meta: db_table = 'cidr' class NullCidrTestModel(Model): field = CidrAddressField(null=True) objects = NetManager() class Meta: db_table = 'nullcidr' class UniqueCidrTestModel(Model): field = CidrAddressField(unique=True) objects = NetManager() class Meta: db_table = 'uniquecidr' class MACTestModel(Model): field = MACAddressField(null=True) objects = NetManager() class Meta: db_table = 'mac' class InetArrayTestModel(Model): field = ArrayField(InetAddressField(), blank=True, null=True) class Meta: db_table = 'inetarray' class CidrArrayTestModel(Model): field = ArrayField(CidrAddressField(), blank=True, null=True) class Meta: db_table = 'cidrarray' class MACArrayTestModel(Model): field = ArrayField(MACAddressField(), blank=True, null=True) class Meta: db_table = 'macarray' class AggregateTestModel(Model): pass class AggregateTestChildModel(Model): parent = ForeignKey('AggregateTestModel', related_name='children') network = CidrAddressField() inet = InetAddressField() django-netfields-0.8/test/tests/0000755000175000001440000000000013212123563017211 5ustar jfunkusers00000000000000django-netfields-0.8/test/tests/test_form_fields.py0000644000175000001440000002163113073312234023116 0ustar jfunkusers00000000000000from __future__ import unicode_literals from ipaddress import ip_address, ip_interface, ip_network from netaddr import EUI from django.forms import ModelForm from django.test import TestCase from test.models import ( CidrTestModel, InetTestModel, UniqueInetTestModel, UniqueCidrTestModel, NoPrefixInetTestModel, MACTestModel ) from netfields.mac import mac_unix_common class InetAddressTestModelForm(ModelForm): class Meta: model = InetTestModel exclude = [] class TestInetAddressFormField(TestCase): form_class = InetAddressTestModelForm def test_form_ipv4_valid(self): form = self.form_class({'field': '10.0.0.1'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_interface('10.0.0.1')) def test_form_ipv4_invalid(self): form = self.form_class({'field': '10.0.0.1.2'}) self.assertFalse(form.is_valid()) def test_form_ipv4_strip(self): form = self.form_class({'field': ' 10.0.0.1 '}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_interface('10.0.0.1')) def test_form_ipv4_change(self): instance = InetTestModel.objects.create(field='10.1.2.3/24') form = self.form_class({'field': '10.1.2.4/24'}, instance=instance) self.assertTrue(form.is_valid()) form.save() instance = InetTestModel.objects.get(pk=instance.pk) self.assertEqual(instance.field, ip_interface('10.1.2.4/24')) def test_form_ipv6_valid(self): form = self.form_class({'field': '2001:0:1::2'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_interface('2001:0:1::2')) def test_form_ipv6_invalid(self): form = self.form_class({'field': '2001:0::1::2'}) self.assertFalse(form.is_valid()) def test_form_ipv6_strip(self): form = self.form_class({'field': ' 2001:0:1::2 '}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_interface('2001:0:1::2')) def test_form_ipv6_change(self): instance = InetTestModel.objects.create(field='2001:0:1::2/64') form = self.form_class({'field': '2001:0:1::3/64'}, instance=instance) self.assertTrue(form.is_valid()) form.save() instance = InetTestModel.objects.get(pk=instance.pk) self.assertEqual(instance.field, ip_interface('2001:0:1::3/64')) class NoPrefixInetAddressTestModelForm(ModelForm): class Meta: model = NoPrefixInetTestModel exclude = [] class TestNoPrefixInetAddressFormField(TestCase): form_class = NoPrefixInetAddressTestModelForm def test_form_ipv4_valid(self): form = self.form_class({'field': '10.0.0.1'}) self.assertTrue(form.is_valid()) # Form always passes ip_interface. Model field will return the requested type self.assertEqual(form.cleaned_data['field'], ip_interface('10.0.0.1')) def test_form_ipv4_invalid(self): form = self.form_class({'field': '10.0.0.1.2'}) self.assertFalse(form.is_valid()) def test_form_ipv4_strip(self): form = self.form_class({'field': ' 10.0.0.1 '}) self.assertTrue(form.is_valid()) # Form always passes ip_interface. Model field will return the requested type self.assertEqual(form.cleaned_data['field'], ip_interface('10.0.0.1')) def test_form_ipv4_change(self): instance = NoPrefixInetTestModel.objects.create(field='10.1.2.3/24') form = self.form_class({'field': '10.1.2.4/24'}, instance=instance) self.assertTrue(form.is_valid()) form.save() instance = NoPrefixInetTestModel.objects.get(pk=instance.pk) self.assertEqual(instance.field, ip_address('10.1.2.4')) def test_form_ipv6_valid(self): form = self.form_class({'field': '2001:0:1::2'}) self.assertTrue(form.is_valid()) # Form always passes ip_interface. Model field will return the requested type self.assertEqual(form.cleaned_data['field'], ip_interface('2001:0:1::2')) def test_form_ipv6_invalid(self): form = self.form_class({'field': '2001:0::1::2'}) self.assertFalse(form.is_valid()) def test_form_ipv6_strip(self): form = self.form_class({'field': ' 2001:0:1::2 '}) self.assertTrue(form.is_valid()) # Form always passes ip_interface. Model field will return the requested type self.assertEqual(form.cleaned_data['field'], ip_interface('2001:0:1::2')) def test_form_ipv6_change(self): instance = NoPrefixInetTestModel.objects.create(field='2001:0:1::2/64') form = self.form_class({'field': '2001:0:1::3/64'}, instance=instance) self.assertTrue(form.is_valid()) form.save() instance = NoPrefixInetTestModel.objects.get(pk=instance.pk) self.assertEqual(instance.field, ip_address('2001:0:1::3')) class UniqueInetAddressTestModelForm(ModelForm): class Meta: model = UniqueInetTestModel exclude = [] class TestUniqueInetAddressFormField(TestInetAddressFormField): form_class = UniqueInetAddressTestModelForm class CidrAddressTestModelForm(ModelForm): class Meta: model = CidrTestModel exclude = [] class TestCidrAddressFormField(TestCase): form_class = CidrAddressTestModelForm def test_form_ipv4_valid(self): form = self.form_class({'field': '10.0.1.0/24'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_network('10.0.1.0/24')) def test_form_ipv4_invalid(self): form = self.form_class({'field': '10.0.0.1.2/32'}) self.assertFalse(form.is_valid()) def test_form_ipv4_strip(self): form = self.form_class({'field': ' 10.0.1.0/24 '}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_network('10.0.1.0/24')) def test_form_ipv4_bits_to_right_of_mask(self): form = self.form_class({'field': '10.0.0.1.2/24'}) self.assertFalse(form.is_valid()) def test_form_ipv6_valid(self): form = self.form_class({'field': '2001:0:1::/64'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_network('2001:0:1::/64')) def test_form_ipv6_invalid(self): form = self.form_class({'field': '2001:0::1::2/128'}) self.assertFalse(form.is_valid()) def test_form_ipv6_strip(self): form = self.form_class({'field': ' 2001:0:1::/64 '}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], ip_network('2001:0:1::/64')) def test_form_ipv6_bits_to_right_of_mask(self): form = self.form_class({'field': '2001:0::1::2/64'}) self.assertFalse(form.is_valid()) class UniqueCidrAddressTestModelForm(ModelForm): class Meta: model = UniqueCidrTestModel exclude = [] class TestUniqueCidrAddressFormField(TestCidrAddressFormField): form_class = UniqueCidrAddressTestModelForm class MacAddressTestModelForm(ModelForm): class Meta: model = MACTestModel exclude = [] class TestMacAddressFormField(TestCase): def setUp(self): self.mac = EUI('00:aa:2b:c3:dd:44', dialect=mac_unix_common) def test_unix(self): form = MacAddressTestModelForm({'field': '0:AA:2b:c3:dd:44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_unix_common(self): form = MacAddressTestModelForm({'field': '00:aa:2b:c3:dd:44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_eui48(self): form = MacAddressTestModelForm({'field': '00-AA-2B-C3-DD-44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_cisco(self): form = MacAddressTestModelForm({'field': '00aa.2bc3.dd44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_24bit_colon(self): form = MacAddressTestModelForm({'field': '00aa2b:c3dd44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_24bit_hyphen(self): form = MacAddressTestModelForm({'field': '00aa2b-c3dd44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_bare(self): form = MacAddressTestModelForm({'field': '00aa2b:c3dd44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_strip(self): form = MacAddressTestModelForm({'field': ' 00:aa:2b:c3:dd:44 '}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_invalid(self): form = MacAddressTestModelForm({'field': 'notvalid'}) self.assertFalse(form.is_valid()) django-netfields-0.8/test/tests/test_sql_fields.py0000644000175000001440000004750513073312234022762 0ustar jfunkusers00000000000000from __future__ import unicode_literals import django from django import VERSION from django.core.exceptions import ValidationError from ipaddress import ( IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network, ip_address, ip_interface, ip_network, ) from netaddr import EUI from django.db import IntegrityError from django.db.models.sql import EmptyResultSet from django.core.exceptions import FieldError from django.test import TestCase from unittest import skipIf from test.models import ( CidrArrayTestModel, CidrTestModel, InetArrayTestModel, InetTestModel, NullCidrTestModel, NullInetTestModel, UniqueInetTestModel, UniqueCidrTestModel, NoPrefixInetTestModel, MACArrayTestModel, MACTestModel, AggregateTestModel, AggregateTestChildModel ) class BaseSqlTestCase(object): select = u'SELECT "table"."id", "table"."field" FROM "table" ' def assertSqlEquals(self, qs, sql): sql = sql.replace('"table"', '"%s"' % self.table) self.assertEqual( qs.query.get_compiler(qs.db).as_sql()[0].strip().lower(), sql.strip().lower() ) def compile_queryset(self, qs): qs.query.get_compiler(qs.db).as_sql() def test_init_with_blank(self): self.model() def test_isnull_true_lookup(self): self.assertSqlEquals( self.qs.filter(field__isnull=True), self.select + 'WHERE "table"."field" IS NULL' ) def test_isnull_false_lookup(self): self.assertSqlEquals( self.qs.filter(field__isnull=False), self.select + 'WHERE "table"."field" IS NOT NULL' ) def test_save(self): self.model(field=self.value1).save() def test_equals_lookup(self): self.assertSqlEquals( self.qs.filter(field=self.value1), self.select + 'WHERE "table"."field" = %s' ) def test_exact_lookup(self): self.assertSqlEquals( self.qs.filter(field__exact=self.value1), self.select + 'WHERE "table"."field" = %s' ) def test_in_lookup(self): self.assertSqlEquals( self.qs.filter(field__in=[self.value1, self.value2]), self.select + 'WHERE "table"."field" IN (%s, %s)' ) def test_in_single_lookup(self): self.assertSqlEquals( self.qs.filter(field__in=[self.value1]), self.select + 'WHERE "table"."field" IN (%s)' ) def test_in_empty_lookup(self): with self.assertRaises(EmptyResultSet): self.qs.filter(field__in=[]).query.get_compiler(self.qs.db).as_sql() def test_gt_lookup(self): self.assertSqlEquals( self.qs.filter(field__gt=self.value1), self.select + 'WHERE "table"."field" > %s' ) def test_gte_lookup(self): self.assertSqlEquals( self.qs.filter(field__gte=self.value1), self.select + 'WHERE "table"."field" >= %s' ) def test_lt_lookup(self): self.assertSqlEquals( self.qs.filter(field__lt=self.value1), self.select + 'WHERE "table"."field" < %s' ) def test_lte_lookup(self): self.assertSqlEquals( self.qs.filter(field__lte=self.value1), self.select + 'WHERE "table"."field" <= %s' ) def test_range_lookup(self): self.assertSqlEquals( self.qs.filter(field__range=(self.value1, self.value3)), self.select + 'WHERE "table"."field" BETWEEN %s AND %s' ) class BaseInetTestCase(BaseSqlTestCase): def test_save_object(self): self.model(field=self.value1).save() def test_save_with_text_fails(self): self.assertRaises(ValidationError, self.model.objects.create, field='abc') def test_iexact_lookup(self): self.assertSqlEquals( self.qs.filter(field__iexact=self.value1), self.select + 'WHERE UPPER("table"."field"::text) = UPPER(%s)' ) def test_search_lookup_fails(self): with self.assertRaises(NotImplementedError): self.compile_queryset(self.qs.filter(field__search='10')) def test_year_lookup_fails(self): with self.assertRaises(FieldError): self.compile_queryset(self.qs.filter(field__year=1)) def test_month_lookup_fails(self): with self.assertRaises(FieldError): self.compile_queryset(self.qs.filter(field__month=1)) def test_day_lookup_fails(self): with self.assertRaises(FieldError): self.compile_queryset(self.qs.filter(field__day=1)) def test_net_contained(self): self.assertSqlEquals( self.qs.filter(field__net_contained='10.0.0.0/24'), 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.0/24'), self.select + 'WHERE "table"."field" <<= %s' ) def test_net_overlaps(self): self.assertSqlEquals( self.qs.filter(field__net_overlaps='10.0.0.0/24'), self.select + 'WHERE "table"."field" && %s', ) def test_family_lookup(self): self.assertSqlEquals( self.qs.filter(field__family=4), self.select + 'WHERE family("table"."field") = %s' ) class BaseInetFieldTestCase(BaseInetTestCase): value1 = '10.0.0.1' value2 = '10.0.0.2/24' value3 = '10.0.0.10' def test_startswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__startswith='10.'), self.select + 'WHERE HOST("table"."field") LIKE %s' ) def test_istartswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__istartswith='10.'), self.select + 'WHERE HOST("table"."field") LIKE UPPER(%s)' ) def test_endswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__endswith='.1'), self.select + 'WHERE HOST("table"."field") LIKE %s' ) def test_iendswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__iendswith='.1'), self.select + 'WHERE HOST("table"."field") LIKE UPPER(%s)' ) def test_regex_lookup(self): self.assertSqlEquals( self.qs.filter(field__regex='10'), self.select + 'WHERE HOST("table"."field") ~ %s' ) def test_iregex_lookup(self): self.assertSqlEquals( self.qs.filter(field__iregex='10'), self.select + 'WHERE HOST("table"."field") ~* %s' ) def test_query_filter_str(self): self.model.objects.filter(field='1.2.3.4') def test_query_filter_ipaddress(self): self.model.objects.filter(field=ip_interface('1.2.3.4')) def test_query_filter_contains_ipnetwork(self): self.model.objects.filter(field__net_contains=ip_network(u'2001::0/16')) class BaseCidrFieldTestCase(BaseInetTestCase): value1 = '10.0.0.1/32' value2 = '10.0.2.0/24' value3 = '10.5.0.0/16' def test_startswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__startswith='10.'), self.select + 'WHERE TEXT("table"."field") LIKE %s' ) def test_istartswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__istartswith='10.'), self.select + 'WHERE TEXT("table"."field") LIKE UPPER(%s)' ) def test_endswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__endswith='.1'), self.select + 'WHERE TEXT("table"."field") LIKE %s' ) def test_iendswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__iendswith='.1'), self.select + 'WHERE TEXT("table"."field") LIKE UPPER(%s)' ) def test_regex_lookup(self): self.assertSqlEquals( self.qs.filter(field__regex='10'), self.select + 'WHERE TEXT("table"."field") ~ %s' ) def test_iregex_lookup(self): 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_query_filter_str(self): self.model.objects.filter(field='1.2.3.0/24') def test_query_filter_ipnetwork(self): self.model.objects.filter(field=ip_network('1.2.3.0/24')) def test_max_prefixlen(self): self.assertSqlEquals( self.qs.filter(field__max_prefixlen='16'), self.select + 'WHERE masklen("table"."field") <= %s' ) def test_min_prefixlen(self): self.assertSqlEquals( self.qs.filter(field__min_prefixlen='16'), self.select + 'WHERE masklen("table"."field") >= %s' ) def test_prefixlen(self): self.assertSqlEquals( self.qs.filter(field__prefixlen='16'), self.select + 'WHERE masklen("table"."field") = %s' ) class TestInetField(BaseInetFieldTestCase, TestCase): def setUp(self): self.model = InetTestModel self.qs = self.model.objects.all() self.table = 'inet' def test_save_blank_fails(self): self.assertRaises(IntegrityError, self.model(field='').save) def test_save_none_fails(self): self.assertRaises(IntegrityError, self.model(field=None).save) def test_save_nothing_fails(self): self.assertRaises(IntegrityError, self.model().save) def test_save_accepts_bytes(self): self.model(field=b'1.1.1.1/24').save() def test_retrieves_ipv4_ipinterface_type(self): instance = self.model.objects.create(field='10.1.2.3/24') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, IPv4Interface) def test_retrieves_ipv6_ipinterface_type(self): instance = self.model.objects.create(field='2001:db8::1/32') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, IPv6Interface) def test_save_preserves_prefix_length(self): instance = self.model.objects.create(field='10.1.2.3/24') instance = self.model.objects.get(pk=instance.pk) self.assertEqual(str(instance.field), '10.1.2.3/24') class TestInetFieldNullable(BaseInetFieldTestCase, TestCase): def setUp(self): self.model = NullInetTestModel self.qs = self.model.objects.all() self.table = 'nullinet' def test_save_blank(self): self.model().save() def test_save_none(self): self.model(field=None).save() def test_save_nothing_fails(self): self.model().save() class TestInetFieldUnique(BaseInetFieldTestCase, TestCase): def setUp(self): self.model = UniqueInetTestModel self.qs = self.model.objects.all() self.table = 'uniqueinet' def test_save_nonunique(self): self.model(field='1.2.3.4').save() self.assertRaises(IntegrityError, self.model(field='1.2.3.4').save) class TestInetFieldNoPrefix(BaseInetFieldTestCase, TestCase): def setUp(self): self.model = NoPrefixInetTestModel self.qs = self.model.objects.all() self.table = 'noprefixinet' def test_save_truncates_prefix_length(self): instance = self.model.objects.create(field='10.1.2.3/24') instance = self.model.objects.get(pk=instance.pk) self.assertEqual(str(instance.field), '10.1.2.3') def test_retrieves_ipv4_ipaddress_type(self): instance = self.model.objects.create(field='10.1.2.3/24') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, IPv4Address) def test_retrieves_ipv6_ipaddress_type(self): instance = self.model.objects.create(field='2001:db8::1/32') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, IPv6Address) class TestCidrField(BaseCidrFieldTestCase, TestCase): def setUp(self): self.model = CidrTestModel self.qs = self.model.objects.all() self.table = 'cidr' def test_save_blank_fails(self): self.assertRaises(IntegrityError, self.model(field='').save) def test_save_none_fails(self): self.assertRaises(IntegrityError, self.model(field=None).save) def test_save_nothing_fails(self): self.assertRaises(IntegrityError, self.model().save) def test_retrieves_ipv4_ipnetwork_type(self): instance = self.model.objects.create(field='10.1.2.0/24') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, IPv4Network) def test_retrieves_ipv6_ipnetwork_type(self): instance = self.model.objects.create(field='2001:db8::0/32') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, IPv6Network) class TestCidrFieldNullable(BaseCidrFieldTestCase, TestCase): def setUp(self): self.model = NullCidrTestModel self.qs = self.model.objects.all() self.table = 'nullcidr' def test_save_blank(self): self.model().save() def test_save_none(self): self.model(field=None).save() def test_save_nothing_fails(self): self.model().save() class TestCidrFieldUnique(BaseCidrFieldTestCase, TestCase): def setUp(self): self.model = UniqueCidrTestModel self.qs = self.model.objects.all() self.table = 'uniquecidr' def test_save_nonunique(self): self.model(field='1.2.3.0/24').save() self.assertRaises(IntegrityError, self.model(field='1.2.3.0/24').save) class BaseMacTestCase(BaseSqlTestCase): value1 = '00:aa:2b:c3:dd:44' value2 = '00:aa:2b:c3:dd:45' value3 = '00:aa:2b:c3:dd:ff' def test_save_object(self): self.model(field=EUI(self.value1)).save() def test_iexact_lookup(self): self.assertSqlEquals( self.qs.filter(field__iexact=self.value1), self.select + 'WHERE UPPER("table"."field"::text) = UPPER(%s)' ) def test_startswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__startswith='00:'), self.select + 'WHERE "table"."field"::text LIKE %s' ) def test_istartswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__istartswith='00:'), self.select + 'WHERE UPPER("table"."field"::text) LIKE UPPER(%s)' ) def test_endswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__endswith=':ff'), self.select + 'WHERE "table"."field"::text LIKE %s' ) def test_iendswith_lookup(self): self.assertSqlEquals( self.qs.filter(field__iendswith=':ff'), self.select + 'WHERE UPPER("table"."field"::text) LIKE UPPER(%s)' ) def test_regex_lookup(self): self.assertSqlEquals( self.qs.filter(field__regex='00'), self.select + 'WHERE "table"."field"::text ~ %s' ) def test_iregex_lookup(self): self.assertSqlEquals( self.qs.filter(field__iregex='00'), self.select + 'WHERE "table"."field"::text ~* %s' ) class TestMacAddressField(BaseMacTestCase, TestCase): def setUp(self): self.model = MACTestModel self.qs = self.model.objects.all() self.table = 'mac' def test_save_blank(self): self.model().save() def test_save_none(self): self.model(field=None).save() def test_save_nothing_fails(self): self.model().save() def test_invalid_fails(self): self.assertRaises(ValidationError, lambda: self.model(field='foobar').save()) def test_retrieves_eui_type(self): instance = self.model.objects.create(field='00:aa:2b:c3:dd:44') instance = self.model.objects.get(pk=instance.pk) self.assertIsInstance(instance.field, EUI) class TestInetAddressFieldArray(TestCase): def test_save_null(self): InetArrayTestModel().save() def test_save_single_item(self): InetArrayTestModel(field=['10.1.1.1/24']).save() def test_save_multiple_items(self): InetArrayTestModel(field=['10.1.1.1', '10.1.1.2']).save() @skipIf(VERSION < (1, 10), 'ArrayField does not return correct types in Django < 1.10. https://code.djangoproject.com/ticket/25143') def test_retrieves_ipv4_ipinterface_type(self): instance = InetArrayTestModel(field=['10.1.1.1/24']) instance.save() instance = InetArrayTestModel.objects.get(id=instance.id) self.assertEqual(instance.field, [IPv4Interface('10.1.1.1/24')]) self.assertIsInstance(instance.field[0], IPv4Interface) class TestCidrAddressFieldArray(TestCase): def test_save_null(self): CidrArrayTestModel().save() def test_save_single_item(self): CidrArrayTestModel(field=['10.1.1.0/24']).save() def test_save_multiple_items(self): CidrArrayTestModel(field=['10.1.1.0/24', '10.1.2.0/24']).save() @skipIf(VERSION < (1, 10), 'ArrayField does not return correct types in Django < 1.10. https://code.djangoproject.com/ticket/25143') def test_retrieves_ipv4_ipnetwork_type(self): instance = CidrArrayTestModel(field=['10.1.1.0/24']) instance.save() instance = CidrArrayTestModel.objects.get(id=instance.id) self.assertEqual(instance.field, [IPv4Network('10.1.1.0/24')]) self.assertIsInstance(instance.field[0], IPv4Network) class TestMACAddressFieldArray(TestCase): def test_save_null(self): MACArrayTestModel().save() def test_save_single_item(self): MACArrayTestModel(field=['00:aa:2b:c3:dd:44']).save() def test_save_multiple_items(self): MACArrayTestModel(field=['00:aa:2b:c3:dd:44', '00:aa:2b:c3:dd:45']).save() @skipIf(VERSION < (1, 10), 'ArrayField does not return correct types in Django < 1.10. https://code.djangoproject.com/ticket/25143') def test_retrieves_eui_type(self): instance = MACArrayTestModel(field=['00:aa:2b:c3:dd:44']) instance.save() instance = MACArrayTestModel.objects.get(id=instance.id) self.assertEqual(instance.field, [EUI('00:aa:2b:c3:dd:44')]) self.assertIsInstance(instance.field[0], EUI) class TestAggegate(TestCase): @skipIf(VERSION < (1, 9), 'Postgres aggregates not supported in Django < 1.9') def test_aggregate_inet(self): from django.contrib.postgres.aggregates import ArrayAgg inet = IPv4Interface('10.20.30.20/32') network = IPv4Network('10.10.10.10/32') parent = AggregateTestModel.objects.create() inet_qs = AggregateTestModel.objects.annotate(agg_inet=ArrayAgg('children__inet')) self.assertEqual(inet_qs[0].agg_inet, []) AggregateTestChildModel.objects.create(parent=parent, network=network, inet=inet) self.assertEqual(inet_qs[0].agg_inet, [inet]) @skipIf(VERSION < (1, 9), 'Postgres aggregates not supported in Django < 1.9') def test_aggregate_network(self): from django.contrib.postgres.aggregates import ArrayAgg inet = IPv4Interface('10.20.30.20/32') network = IPv4Network('10.10.10.10/32') parent = AggregateTestModel.objects.create() network_qs = AggregateTestModel.objects.annotate(agg_network=ArrayAgg('children__network')) self.assertEqual(network_qs[0].agg_network, []) AggregateTestChildModel.objects.create(parent=parent, network=network, inet=inet) self.assertEqual(network_qs[0].agg_network, [network]) django-netfields-0.8/test/tests/test_rest_framework_fields.py0000644000175000001440000000452213073312234025205 0ustar jfunkusers00000000000000from __future__ import absolute_import, unicode_literals from rest_framework import serializers import unittest2 as unittest from netfields import rest_framework as fields class FieldsTestCase(unittest.TestCase): def test_validation_inet_field(self): class TestSerializer(serializers.Serializer): ip = fields.InetAddressField() address = '10.0.0.' serializer = TestSerializer(data={'ip': address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertEqual(e.exception.detail['ip'], ["Invalid IP address: %r does not appear to be an IPv4 or IPv6 interface" % address]) def test_validation_cidr_field(self): class TestSerializer(serializers.Serializer): cidr = fields.CidrAddressField() address = '10.0.0.' serializer = TestSerializer(data={'cidr': address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertEqual(e.exception.detail['cidr'], ["Invalid CIDR address: %r does not appear to be an IPv4 or IPv6 network" % address]) def test_validation_mac_field(self): class TestSerializer(serializers.Serializer): mac = fields.MACAddressField() address = 'de:' serializer = TestSerializer(data={'mac': address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertEqual(e.exception.detail['mac'], ["Invalid MAC address: failed to detect EUI version: %r" % address]) def test_validation_additional_validators(self): def validate(value): raise serializers.ValidationError('Invalid.') class TestSerializer(serializers.Serializer): ip = fields.InetAddressField(validators=[validate]) address = 'de:' serializer = TestSerializer(data={'ip': address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertItemsEqual(e.exception.detail['ip'], ["Invalid IP address: %r does not appear to be an IPv4 or IPv6 interface" % address, 'Invalid.']) django-netfields-0.8/test/tests/__init__.py0000644000175000001440000000000012612202025021302 0ustar jfunkusers00000000000000django-netfields-0.8/test/__init__.py0000644000175000001440000000000012611625041020146 0ustar jfunkusers00000000000000django-netfields-0.8/manage.py0000755000175000001440000000036612471552614016713 0ustar jfunkusers00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testsettings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-netfields-0.8/netfields/0000755000175000001440000000000013212123563017045 5ustar jfunkusers00000000000000django-netfields-0.8/netfields/fields.py0000644000175000001440000001447713172022407020702 0ustar jfunkusers00000000000000from django.core.exceptions import ValidationError from django.db import models from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper from django.utils.six import with_metaclass, text_type from ipaddress import ip_interface, ip_network from netaddr import EUI from netaddr.core import AddrFormatError from netfields.forms import InetAddressFormField, CidrAddressFormField, MACAddressFormField from netfields.mac import mac_unix_common from netfields.psycopg2_types import Inet, Macaddr NET_OPERATORS = DatabaseWrapper.operators.copy() for operator in ['contains', 'startswith', 'endswith']: NET_OPERATORS[operator] = 'ILIKE %s' NET_OPERATORS['i%s' % operator] = 'ILIKE %s' NET_OPERATORS['iexact'] = NET_OPERATORS['exact'] NET_OPERATORS['regex'] = NET_OPERATORS['iregex'] NET_OPERATORS['net_contained'] = '<< %s' NET_OPERATORS['net_contained_or_equal'] = '<<= %s' NET_OPERATORS['net_contains'] = '>> %s' NET_OPERATORS['net_contains_or_equals'] = '>>= %s' NET_OPERATORS['net_overlaps'] = '&& %s' NET_OPERATORS['max_prefixlen'] = '%s' NET_OPERATORS['min_prefixlen'] = '%s' NET_TEXT_OPERATORS = ['ILIKE %s', '~* %s'] class _NetAddressField(models.Field): empty_strings_allowed = False def __init__(self, *args, **kwargs): kwargs['max_length'] = self.max_length super(_NetAddressField, self).__init__(*args, **kwargs) def from_db_value(self, value, expression, connection, context): if isinstance(value, list): # Aggregation detected, return a list of values return [self.to_python(v) for v in value if v is not None] return self.to_python(value) def to_python(self, value): if not value: return value if isinstance(value, bytes): value = value.decode('ascii') try: return self.python_type()(value) except ValueError as e: raise ValidationError(e) def get_prep_lookup(self, lookup_type, value): if (lookup_type in NET_OPERATORS and NET_OPERATORS[lookup_type] not in NET_TEXT_OPERATORS): if (lookup_type.startswith('net_contained') or lookup_type.endswith('prefixlen')) and value is not None: # Argument will be CIDR return str(value) return self.get_prep_value(value) return super(_NetAddressField, self).get_prep_lookup( lookup_type, value) def get_prep_value(self, value): if not value: return None return str(self.to_python(value)) def get_db_prep_value(self, value, connection, prepared=False): # Django <= 1.8, ArrayField does not pass model to the base_field so we have to check for existance model = getattr(self, 'model', None) if model is None or model._meta.get_field(self.name).get_internal_type() == 'ArrayField': is_array_field = True else: is_array_field = False if prepared is False and is_array_field is False: return self.get_prep_value(value) return Inet(self.get_prep_value(value)) def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): if not value: return [] if (lookup_type in NET_OPERATORS and NET_OPERATORS[lookup_type] not in NET_TEXT_OPERATORS): return [value] if prepared else [self.get_prep_value(value)] return super(_NetAddressField, self).get_db_prep_lookup( lookup_type, value, connection=connection, prepared=prepared) def formfield(self, **kwargs): defaults = {'form_class': self.form_class()} defaults.update(kwargs) return super(_NetAddressField, self).formfield(**defaults) def deconstruct(self): name, path, args, kwargs = super(_NetAddressField, self).deconstruct() if self.max_length is not None: kwargs['max_length'] = self.max_length return name, path, args, kwargs class InetAddressField(_NetAddressField): description = "PostgreSQL INET field" max_length = 39 def __init__(self, *args, **kwargs): self.store_prefix_length = kwargs.pop('store_prefix_length', True) super(InetAddressField, self).__init__(*args, **kwargs) def db_type(self, connection): return 'inet' def python_type(self): return ip_interface def to_python(self, value): value = super(InetAddressField, self).to_python(value) if value: if self.store_prefix_length: return value else: return value.ip return value def form_class(self): return InetAddressFormField class CidrAddressField(_NetAddressField): description = "PostgreSQL CIDR field" max_length = 43 python_type = ip_network def db_type(self, connection): return 'cidr' def python_type(self): return ip_network def form_class(self): return CidrAddressFormField class MACAddressField(models.Field): description = "PostgreSQL MACADDR field" max_length = 17 def db_type(self, connection): return 'macaddr' def from_db_value(self, value, expression, connection, context): return self.to_python(value) def to_python(self, value): if not value: return value try: return EUI(value, dialect=mac_unix_common) except AddrFormatError as e: raise ValidationError(e) def get_prep_value(self, value): if not value: return None return text_type(self.to_python(value)) def get_db_prep_value(self, value, connection, prepared=False): # Django <= 1.8, ArrayField does not pass model to the base_field so we have to check for existance model = getattr(self, 'model', None) if model is None or model._meta.get_field(self.name).get_internal_type() == 'ArrayField': is_array_field = True else: is_array_field = False if prepared is False and is_array_field is False: return self.get_prep_value(value) return Macaddr(self.get_prep_value(value)) def formfield(self, **kwargs): defaults = {'form_class': MACAddressFormField} defaults.update(kwargs) return super(MACAddressField, self).formfield(**defaults) django-netfields-0.8/netfields/models.py0000644000175000001440000000000012611625041020670 0ustar jfunkusers00000000000000django-netfields-0.8/netfields/lookups.py0000644000175000001440000001160013172020115021103 0ustar jfunkusers00000000000000from django.core.exceptions import FieldError from django.db.models import Lookup, Transform, IntegerField from django.db.models.lookups import EndsWith, IEndsWith, StartsWith, IStartsWith, Regex, IRegex import ipaddress from netfields.fields import InetAddressField, CidrAddressField class InvalidLookup(Lookup): """ Emulate Django 1.9 error for unsupported lookups """ def as_sql(self, qn, connection): raise FieldError("Unsupported lookup '%s'" % self.lookup_name) class InvalidSearchLookup(Lookup): """ Emulate Django 1.9 error for unsupported search lookup """ lookup_name = 'search' def as_sql(self, qn, connection): raise NotImplementedError("Full-text search is not implemented for this database backend") class NetFieldDecoratorMixin(object): def process_lhs(self, qn, connection, lhs=None): lhs = lhs or self.lhs lhs_string, lhs_params = qn.compile(lhs) if isinstance(lhs.source if hasattr(lhs, 'source') else lhs.output_field, InetAddressField): lhs_string = 'HOST(%s)' % lhs_string elif isinstance(lhs.source if hasattr(lhs, 'source') else lhs.output_field, CidrAddressField): lhs_string = 'TEXT(%s)' % lhs_string return lhs_string, lhs_params class EndsWith(NetFieldDecoratorMixin, EndsWith): pass class IEndsWith(NetFieldDecoratorMixin, IEndsWith): pass class StartsWith(NetFieldDecoratorMixin, StartsWith): pass class IStartsWith(NetFieldDecoratorMixin, IStartsWith): pass class Regex(NetFieldDecoratorMixin, Regex): pass class IRegex(NetFieldDecoratorMixin, IRegex): pass class NetworkLookup(object): def get_prep_lookup(self): if isinstance(self.rhs, ipaddress._BaseNetwork): return str(self.rhs) return str(ipaddress.ip_network(self.rhs)) class AddressLookup(object): def get_prep_lookup(self): if isinstance(self.rhs, ipaddress._BaseAddress): return str(self.rhs) return str(ipaddress.ip_interface(self.rhs)) class NetContains(AddressLookup, Lookup): lookup_name = 'net_contains' def as_sql(self, qn, connection): lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return '%s >> %s' % (lhs, rhs), params class NetContained(NetworkLookup, Lookup): lookup_name = 'net_contained' def as_sql(self, qn, connection): lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return '%s << %s' % (lhs, rhs), params class NetContainsOrEquals(AddressLookup, Lookup): lookup_name = 'net_contains_or_equals' def as_sql(self, qn, connection): lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return '%s >>= %s' % (lhs, rhs), params class NetContainedOrEqual(NetworkLookup, Lookup): lookup_name = 'net_contained_or_equal' def as_sql(self, qn, connection): lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return '%s <<= %s' % (lhs, rhs), params class NetOverlaps(NetworkLookup, Lookup): lookup_name = 'net_overlaps' def as_sql(self, qn, connection): lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return '%s && %s' % (lhs, rhs), params class Family(Transform): lookup_name = 'family' def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) return "family(%s)" % lhs, params @property def output_field(self): return IntegerField() class _PrefixlenMixin(object): format_string = None def as_sql(self, qn, connection): assert self.format_string is not None, "Prefixlen lookups must specify a format_string" lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return self.format_string % (lhs, rhs), params def process_lhs(self, qn, connection, lhs=None): lhs = lhs or self.lhs lhs_string, lhs_params = qn.compile(lhs) lhs_string = 'MASKLEN(%s)' % lhs_string return lhs_string, lhs_params def get_prep_lookup(self): return str(int(self.rhs)) class MaxPrefixlen(_PrefixlenMixin, Lookup): lookup_name = 'max_prefixlen' format_string = '%s <= %s' class MinPrefixlen(_PrefixlenMixin, Lookup): lookup_name = 'min_prefixlen' format_string = '%s >= %s' class Prefixlen(_PrefixlenMixin, Lookup): lookup_name = 'prefixlen' format_string = '%s = %s' django-netfields-0.8/netfields/psycopg2_types.py0000644000175000001440000000145713073312234022420 0ustar jfunkusers00000000000000import psycopg2.extensions from psycopg2.extras import Inet class Macaddr(Inet): """ Wrap a string for the MACADDR type, like Inet """ def getquoted(self): obj = psycopg2.extensions.adapt(self.addr) if hasattr(obj, 'prepare'): obj.prepare(self._conn) return obj.getquoted() + b"::macaddr" # Register array types for CIDR and MACADDR (Django already registers INET) CIDRARRAY_OID = 651 CIDRARRAY = psycopg2.extensions.new_array_type( (CIDRARRAY_OID,), 'CIDRARRAY', psycopg2.extensions.UNICODE, ) psycopg2.extensions.register_type(CIDRARRAY) MACADDRARRAY_OID = 1040 MACADDRARRAY = psycopg2.extensions.new_array_type( (MACADDRARRAY_OID,), 'MACADDRARRAY', psycopg2.extensions.UNICODE, ) psycopg2.extensions.register_type(MACADDRARRAY) django-netfields-0.8/netfields/rest_framework.py0000644000175000001440000000205413073312234022452 0ustar jfunkusers00000000000000from __future__ import absolute_import from django.core.exceptions import ValidationError as DjangoValidationError from rest_framework import serializers from netfields import fields class NetfieldsField(serializers.CharField): def __init__(self, *args, **kwargs): super(NetfieldsField, self).__init__(*args, **kwargs) self.validators.append(self._validate_netaddr) def _validate_netaddr(self, value): """Convert Django validation errors to DRF validation errors. """ try: self.netfields_type(value).to_python(value) except DjangoValidationError as e: raise serializers.ValidationError("Invalid {} address: {}".format(self.address_type, e.message)) class InetAddressField(NetfieldsField): netfields_type = fields.InetAddressField address_type = "IP" class CidrAddressField(NetfieldsField): netfields_type = fields.CidrAddressField address_type = "CIDR" class MACAddressField(NetfieldsField): netfields_type = fields.MACAddressField address_type = "MAC" django-netfields-0.8/netfields/managers.py0000644000175000001440000000111713172022614021214 0ustar jfunkusers00000000000000from django.db import models from ipaddress import _BaseNetwork try: str_type = unicode except NameError: str_type = str class NetManager(models.Manager): use_for_related_fields = True def filter(self, *args, **kwargs): for key, val in kwargs.items(): if isinstance(val, _BaseNetwork): # Django will attempt to consume the _BaseNetwork iterator, which # will convert it to a list of every address in the network kwargs[key] = str_type(val) return super(NetManager, self).filter(*args, **kwargs) django-netfields-0.8/netfields/mac.py0000644000175000001440000000023612471552614020171 0ustar jfunkusers00000000000000import netaddr class mac_unix_common(netaddr.mac_eui48): """Common form of UNIX MAC address dialect class""" word_sep = ':' word_fmt = '%.2x' django-netfields-0.8/netfields/__init__.py0000644000175000001440000000032212471561147021165 0ustar jfunkusers00000000000000from netfields.managers import NetManager from netfields.fields import (InetAddressField, CidrAddressField, MACAddressField) default_app_config = 'netfields.apps.NetfieldsConfig' django-netfields-0.8/netfields/forms.py0000644000175000001440000000456113074273631020563 0ustar jfunkusers00000000000000from ipaddress import ip_interface, ip_network, _IPAddressBase, _BaseNetwork from netaddr import EUI, AddrFormatError from django import forms from django.utils.six import text_type from django.core.exceptions import ValidationError from netfields.mac import mac_unix_common class InetAddressFormField(forms.Field): widget = forms.TextInput default_error_messages = { 'invalid': u'Enter a valid IP address.', } def __init__(self, *args, **kwargs): super(InetAddressFormField, self).__init__(*args, **kwargs) def to_python(self, value): if not value: return None if isinstance(value, _IPAddressBase): return value if isinstance(value, text_type): value = value.strip() try: return ip_interface(value) except ValueError as e: raise ValidationError(self.default_error_messages['invalid']) class CidrAddressFormField(forms.Field): widget = forms.TextInput 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, _BaseNetwork): network = value if isinstance(value, text_type): value = value.strip() try: network = ip_network(value) except ValueError as e: raise ValidationError(self.default_error_messages['invalid']) return network class MACAddressFormField(forms.Field): default_error_messages = { 'invalid': u'Enter a valid MAC address.', } def __init__(self, *args, **kwargs): super(MACAddressFormField, self).__init__(*args, **kwargs) def to_python(self, value): if not value: return None if isinstance(value, EUI): return value if isinstance(value, text_type): value = value.strip() try: return EUI(value, dialect=mac_unix_common) except (AddrFormatError, TypeError): raise ValidationError(self.error_messages['invalid']) def widget_attrs(self, widget): attrs = super(MACAddressFormField, self).widget_attrs(widget) attrs.update({'maxlength': '17'}) return attrs django-netfields-0.8/netfields/apps.py0000644000175000001440000000501513004525762020371 0ustar jfunkusers00000000000000import django from django.apps import AppConfig from django.db.models import Field from netfields.fields import CidrAddressField, InetAddressField from netfields.lookups import ( EndsWith, Family, IEndsWith, IRegex, IStartsWith, InvalidLookup, InvalidSearchLookup, MaxPrefixlen, MinPrefixlen, NetContained, NetContainedOrEqual, NetContains, NetContainsOrEquals, NetOverlaps, Prefixlen, Regex, StartsWith, ) class NetfieldsConfig(AppConfig): name = 'netfields' if django.VERSION < (1, 9): for lookup in Field.class_lookups.keys(): if lookup not in ['contains', 'startswith', 'endswith', 'icontains', 'istartswith', 'iendswith', 'isnull', 'in', 'exact', 'iexact', 'regex', 'iregex', 'lt', 'lte', 'gt', 'gte', 'equals', 'iequals', 'range', 'search']: invalid_lookup = InvalidLookup invalid_lookup.lookup_name = lookup CidrAddressField.register_lookup(invalid_lookup) InetAddressField.register_lookup(invalid_lookup) CidrAddressField.register_lookup(InvalidSearchLookup) InetAddressField.register_lookup(InvalidSearchLookup) CidrAddressField.register_lookup(EndsWith) CidrAddressField.register_lookup(IEndsWith) CidrAddressField.register_lookup(StartsWith) CidrAddressField.register_lookup(IStartsWith) CidrAddressField.register_lookup(Regex) CidrAddressField.register_lookup(IRegex) CidrAddressField.register_lookup(NetContained) CidrAddressField.register_lookup(NetContains) CidrAddressField.register_lookup(NetContainedOrEqual) CidrAddressField.register_lookup(NetContainsOrEquals) CidrAddressField.register_lookup(NetOverlaps) CidrAddressField.register_lookup(Family) CidrAddressField.register_lookup(MaxPrefixlen) CidrAddressField.register_lookup(MinPrefixlen) CidrAddressField.register_lookup(Prefixlen) InetAddressField.register_lookup(EndsWith) InetAddressField.register_lookup(IEndsWith) InetAddressField.register_lookup(StartsWith) InetAddressField.register_lookup(IStartsWith) InetAddressField.register_lookup(Regex) InetAddressField.register_lookup(IRegex) InetAddressField.register_lookup(NetContained) InetAddressField.register_lookup(NetContains) InetAddressField.register_lookup(NetContainedOrEqual) InetAddressField.register_lookup(NetContainsOrEquals) InetAddressField.register_lookup(NetOverlaps) InetAddressField.register_lookup(Family) django-netfields-0.8/setup.cfg0000644000175000001440000000004613212123563016711 0ustar jfunkusers00000000000000[egg_info] tag_build = tag_date = 0 django-netfields-0.8/LICENSE0000644000175000001440000000277412471552614016120 0ustar jfunkusers00000000000000Copyright (c) Thomas Adamcik and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Django nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.