pax_global_header00006660000000000000000000000064151523303050014507gustar00rootroot0000000000000052 comment=a6f8cbe98105354292d216bf2bcddff264a24ddf jimfunk-django-postgresql-netfields-a6f8cbe/000077500000000000000000000000001515233030500213465ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/.github/000077500000000000000000000000001515233030500227065ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/.github/workflows/000077500000000000000000000000001515233030500247435ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/.github/workflows/actions.yaml000066400000000000000000000037301515233030500272720ustar00rootroot00000000000000name: django-postgresql-netfields on: - push - pull_request jobs: prepare: runs-on: ubuntu-latest steps: - name: Create matrix uses: fabiocaccamo/create-matrix-action@v2 id: create_matrix with: matrix: | python-version {3.8}, tox-env {py38-django111,py38-django22,py38-django30,py38-django31,py38-django32,py38-django40,py38-django41,py38-django42} python-version {3.9}, tox-env {py39-django111,py39-django22,py39-django30,py39-django31,py39-django32,py39-django40,py39-django41,py39-django42} python-version {3.10}, tox-env {py310-django32,py310-django40,py310-django41,py310-django42} python-version {3.11}, tox-env {py311-django41,py311-django42,py311-django42-psycopg3,py311-django42-psycopg3,py311-django51,py311-django51,py311-django52,py311-django52-psycopg3} python-version {3.12}, tox-env {py312-django42,py312-django42-psycopg3,py312-django42-psycopg3,py312-django51,py312-django51,py312-django52,py312-django52-psycopg3} python-version {3.13}, tox-env {py313-django42,py313-django42-psycopg3,py313-django42-psycopg3,py313-django51,py313-django51,py313-django52,py313-django52-psycopg3} outputs: matrix: ${{ steps.create_matrix.outputs.matrix }} build: runs-on: ubuntu-latest needs: prepare strategy: fail-fast: false matrix: include: ${{fromJson(needs.prepare.outputs.matrix)}} steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | sudo apt install postgresql libpq-dev sudo service postgresql start sudo -u postgres createuser -d runner python -m pip install --upgrade pip python -m pip install tox - name: Test with tox run: tox -e ${{ matrix.tox-env }} jimfunk-django-postgresql-netfields-a6f8cbe/.gitignore000066400000000000000000000000671515233030500233410ustar00rootroot00000000000000*.pyc *.egg *.egg-info .* MANIFEST build/ dist/ *.json jimfunk-django-postgresql-netfields-a6f8cbe/AUTHORS000066400000000000000000000003041515233030500224130ustar00rootroot00000000000000Written 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 jimfunk-django-postgresql-netfields-a6f8cbe/CHANGELOG000066400000000000000000000144221515233030500225630ustar00rootroot00000000000000- 1.4.1 * Strip scopes on IPv6Interface instances #131 - 1.4.0 * Add support for Django 6.0 * Modernize setup - 1.3.2 * Fix uncaught EUI() exceptions (Kyle Birkeland) * Fix deprecation warning for app config in newer versions of Django (Gil Forcada Codinachs) * Add get_placeholder to handle automatic check constraint evaluation (sevdog) - 1.3.1 * Add support for psycopg v3.x in Django 4.2 and above (Clemens Wolff) - 1.3.0 * Added MACAddress8Field for EUI-64 values Benoît Vinot * Fix potential MacAddressField crash in DRF (tfromme) * Tests against newer versions of Python and Django (Benoît Vinot) - 1.2.4 * Change InetAddressField form value based on prefix parameter (Travis MacDonald) - 1.2.3 * Change __prefixlen to be a Transform instead of a Lookup (Christopher Aubut) * Add package name to README installation section (Yuval Adam) * Fix formatting error in README (Gil Forcada Codinachs) * Add NetModelSerializer to rest_framework (Devid) * Handle more invalid macs in DRF MACAddressField (Étienne Noss) * Avoid deprecation warning on Django 2.x - 1.2.2 * Additional Django 3 compatibility (Étienne Noss) - 1.2.1 * django.utils.six is deprecated in Django 3.0 (jaychoo) - 1.2.0 * Django 3.0 support (Erik de Wildt) * Expose INET related PostgreSQL functions. See README.rst for usage (Christopher Aubut) - 1.1.1 * Fix improper conversion of networks to addresses in net lookups. Bug introduced in 1.1.0 * Added example of using a network field as an index (Christopher Aubut) - 1.1.0 * Add rhs lookup expression support (Christopher Aubut) * Update REST Framework InetAddressField to avoid using the wrong type when store_prefix is True (Nick Huber) - 1.0.1 * Added a specific error to form and REST framework fields for host addresses passed to network fields - 1.0.0 * Rewrote the REST framework fields to conform to recommendations + The proper types are now set on unsaved models and passed to validators. Any validators or pre-save code that assumes the values are strings may need to be adjusted + Simplified the validation error messages * Fix deprecation warnings (Mike Lane) * Test against Django 2.2 (Øyvind Kolbu) - 0.10.0 * Added support for __host lookups, for matching the host part of an address regardless of prefix length - 0.9.0 * Added support for Django 2.1 * Some Django 3.0 compatibility * BREAKING CHANGE: If using ArrayAgg to return groups of network addresses, None values will be included as per the Django documentation. Previously, the None values were filtered out, but that appears to have been a mistake in the implementation. If your application depends on the filtered None values, it will need to be updated accordingly * Prefer "error_messages" rather than "default_error_messages" in form fields (Shintaro Kojima) - 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 jimfunk-django-postgresql-netfields-a6f8cbe/LICENSE000066400000000000000000000027741515233030500223650ustar00rootroot00000000000000Copyright (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. jimfunk-django-postgresql-netfields-a6f8cbe/MANIFEST.in000066400000000000000000000002541515233030500231050ustar00rootroot00000000000000include AUTHORS include CHANGELOG include LICENSE include README.rst include requirements.txt include manage.py include testsettings.py include test/* include test/tests/* jimfunk-django-postgresql-netfields-a6f8cbe/README.rst000066400000000000000000000307651515233030500230500ustar00rootroot00000000000000Django PostgreSQL Netfields =========================== 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, ``InetAddressField``, a ``CidrAddressField`` a ``MACAddressField``, and a ``MACAddress8Field`` have been added. This library also provides a manager that allows for advanced IP based lookups 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 fields are represented as EUI types 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 ------------ This module requires ``Django >= 1.11``, ``psycopg2`` or ``psycopg``, and ``netaddr``. Installation ------------ .. code-block:: bash $ pip install django-netfields 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. .. code-block:: python 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. .. code-block:: python 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``). .. code-block:: python from netfields import MACAddressField, NetManager class Example(models.Model): inet = MACAddressField() # ... ``MACAddress8Field`` will store values in PostgreSQL as type ``MACADDR8``. In Python, the value will be represented as a ``netaddr.EUI`` object. As with ``MACAddressField``, the representation is the common one (``00:11:22:aa:bb:cc:dd:ee``). .. code-block:: python from netfields import MACAddress8Field, NetManager class Example(models.Model): inet = MACAddress8Field() # ... 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 ``__host`` matches the host part of an address regardless of prefix length ``__prefixlen`` matches the prefix length part of an address These correspond with the operators and functions from http://www.postgresql.org/docs/9.4/interactive/functions-net.html ``CidrAddressField`` includes two extra lookups (these will be depreciated in the future by ``__prefixlen``): ``__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 Database Functions '''''''''''''''''' `Postgres network address functions `_ are exposed via the ``netfields.functions`` module. They can be used to extract additional information from these fields or to construct complex queries. .. code-block:: python from django.db.models import F from netfields import CidrAddressField, NetManager from netfields.functions import Family, Masklen class Example(models.Model): inet = CidrAddressField() # ... ipv4_with_num_ips = ( Example.objects.annotate( family=Family(F('inet')), num_ips=2 ** (32 - Masklen(F('inet'))) # requires Django >2.0 to resolve ) .filter(family=4) ) **CidrAddressField and InetAddressField Functions** +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | Postgres Function | Django Function | Return Type | Description | +================================+==================+======================+================================================================+ | abbrev(``T``) | Abbrev | ``TextField`` | abbreviated display format as text | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | broadcast(``T``) | Broadcast | ``InetAddressField`` | broadcast address for network | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | family(``T``) | Family | ``IntegerField`` | extract family of address; 4 for IPv4, 6 for IPv6 | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | host(``T``) | Host | ``TextField`` | extract IP address as text | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | hostmask(``T``) | Hostmask | ``InetAddressField`` | construct host mask for network | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | masklen(``T``) | Masklen | ``IntegerField`` | extract netmask length | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | netmask(``T``) | Netmask | ``InetAddressField`` | construct netmask for network | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | network(``T``) | Network | ``CidrAddressField`` | extract network part of address | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | set_masklen(``T``, int) | SetMasklen | ``T`` | set netmask length for inet value | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | text(``T``) | AsText | ``TextField`` | extract IP address and netmask length as text | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | inet_same_family(``T``, ``T``) | IsSameFamily | ``BooleanField`` | are the addresses from the same family? | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | inet_merge(``T``, ``T``) | Merge | ``CidrAddressField`` | the smallest network which includes both of the given networks | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ **MACAddressField Functions** +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | Postgres Function | Django Function | Return Type | Description | +================================+==================+======================+================================================================+ | trunc(``T``) | Trunc | ``T`` | set last 3 bytes to zero | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ **MACAddress8Field Functions** +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | Postgres Function | Django Function | Return Type | Description | +================================+==================+======================+================================================================+ | trunc(``T``) | Trunc | ``T`` | set last 5 bytes to zero | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ | macaddr8_set7bit(``T``) | Macaddr8Set7bit | ``T`` | set 7th bit to one. Used to generate link-local IPv6 addresses | +--------------------------------+------------------+----------------------+----------------------------------------------------------------+ Indexes ''''''' As of Django 2.2, indexes can be created for ``InetAddressField`` and ``CidrAddressField`` extra lookups directly on the model. .. code-block:: python from django.contrib.postgres.indexes import GistIndex from netfields import CidrAddressField, NetManager class Example(models.Model): inet = CidrAddressField() # ... class Meta: indexes = ( GistIndex( fields=('inet',), opclasses=('inet_ops',), name='app_example_inet_idx' ), ) For earlier versions of Django, a custom migration can be used to install an index. .. code-block:: python from django.db import migrations class Migration(migrations.Migration): # ... operations = [ # ... migrations.RunSQL( "CREATE INDEX app_example_inet_idx ON app_example USING GIST (inet inet_ops);" ), # ... ] 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 originally 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 :-) jimfunk-django-postgresql-netfields-a6f8cbe/manage.py000077500000000000000000000003661515233030500231600ustar00rootroot00000000000000#!/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) jimfunk-django-postgresql-netfields-a6f8cbe/netfields/000077500000000000000000000000001515233030500233235ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/netfields/__init__.py000066400000000000000000000005461515233030500254410ustar00rootroot00000000000000from django import VERSION from netfields.managers import NetManager from netfields.fields import (InetAddressField, CidrAddressField, MACAddressField, MACAddress8Field) # only keep it for django 3.1 and below if VERSION[0] < 3 or VERSION[0] == 3 and VERSION[1] < 2: default_app_config = 'netfields.apps.NetfieldsConfig' jimfunk-django-postgresql-netfields-a6f8cbe/netfields/apps.py000066400000000000000000000052621515233030500246450ustar00rootroot00000000000000import 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, HostMatches, ) 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) CidrAddressField.register_lookup(HostMatches) 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) InetAddressField.register_lookup(Prefixlen) InetAddressField.register_lookup(HostMatches) jimfunk-django-postgresql-netfields-a6f8cbe/netfields/compat.py000066400000000000000000000004651515233030500251650ustar00rootroot00000000000000from django import VERSION if VERSION[0] < 2: from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper else: from django.db.backends.postgresql.base import DatabaseWrapper try: from django.db.backends.postgresql.base import is_psycopg3 except ImportError: is_psycopg3 = False jimfunk-django-postgresql-netfields-a6f8cbe/netfields/fields.py000066400000000000000000000211451515233030500251460ustar00rootroot00000000000000from django.core.exceptions import ValidationError from django.db import models from ipaddress import ip_interface, ip_network, IPv6Interface from netaddr import EUI from netaddr.core import AddrFormatError from netfields.compat import DatabaseWrapper, is_psycopg3 from netfields.forms import ( InetAddressFormField, NoPrefixInetAddressFormField, CidrAddressFormField, MACAddressFormField, MACAddress8FormField ) from netfields.mac import mac_unix_common, mac_eui64 if is_psycopg3: from netfields.psycopg3_types import Inet, Macaddr, Macaddr8 else: from netfields.psycopg2_types import Inet, Macaddr, Macaddr8 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, *args): if isinstance(value, list): # Aggregation detected, return a list of values. This is no longer # necessary in Django 2.1 return [self.to_python(v) for v in value] 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 hasattr(value, '_prepare'): try: # Django 1.8 return value._prepare() except TypeError: # Django 1.9 return value._prepare(self) if (lookup_type in NET_OPERATORS and NET_OPERATORS[lookup_type] not in NET_TEXT_OPERATORS): if (lookup_type.startswith('net_') or lookup_type.endswith('prefixlen')) and value is not None: 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): if value is None: return None if self.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): if prepared: return [value] if (lookup_type.startswith('net_') or lookup_type.endswith('prefixlen')) and value is not None: return str(value) return [self.get_prep_value(value)] return super(_NetAddressField, self).get_db_prep_lookup( lookup_type, value, connection=connection, prepared=prepared) def get_placeholder(self, value, compiler, connection): return "%s::{}".format(self.db_type(connection)) 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 get_prep_value(self, value): if not value: return None value = self.to_python(value) # Strip IPv6 scope identifier if it is present if getattr(value, 'scope_id', None): value = IPv6Interface((value.ip, value.network.prefixlen)) return str(value) def form_class(self): if self.store_prefix_length: return InetAddressFormField return NoPrefixInetAddressFormField 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, *args): return self.to_python(value) def to_python(self, value): if not value: return value try: return EUI(value, version=48, dialect=mac_unix_common) except (AddrFormatError, IndexError, TypeError) as e: raise ValidationError(e) 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): if value is None: return None if self.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) class MACAddress8Field(models.Field): """A MAC Address field with 8 bytes""" description = "PostgreSQL MACADDR8 field" max_length = 23 def db_type(self, connection): return "macaddr8" def from_db_value(self, value, expression, connection, *args): return self.to_python(value) def to_python(self, value): if not value: return value try: mac = EUI(value, dialect=mac_eui64) if mac.version == 64: return mac mac = mac.eui64() mac.dialect = mac_eui64 return mac except (AddrFormatError, IndexError, TypeError) as e: raise ValidationError(e) 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): if value is None: return None if self.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 Macaddr8(self.get_prep_value(value)) def formfield(self, **kwargs): defaults = {'form_class': MACAddress8FormField} defaults.update(kwargs) return super(MACAddress8Field, self).formfield(**defaults) jimfunk-django-postgresql-netfields-a6f8cbe/netfields/forms.py000066400000000000000000000100761515233030500250270ustar00rootroot00000000000000from ipaddress import ip_address, ip_interface, ip_network, _IPAddressBase, _BaseNetwork from netaddr import EUI, AddrFormatError from django import forms from django.core.exceptions import ValidationError from netfields.mac import mac_unix_common, mac_eui64 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, str): value = value.strip() try: return ip_interface(value) except ValueError: raise ValidationError(self.error_messages['invalid']) class NoPrefixInetAddressFormField(forms.Field): widget = forms.TextInput default_error_messages = { 'invalid': u'Enter a valid IP address.', } def __init__(self, *args, **kwargs): super(NoPrefixInetAddressFormField, self).__init__(*args, **kwargs) def to_python(self, value): if not value: return None if isinstance(value, _IPAddressBase): return value if isinstance(value, str): value = value.strip() try: return ip_address(value) except ValueError: raise ValidationError(self.error_messages['invalid']) class CidrAddressFormField(forms.Field): widget = forms.TextInput default_error_messages = { 'invalid': u'Enter a valid CIDR address.', 'network': u'Must be a network 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, str): value = value.strip() try: network = ip_network(value) except ValueError as e: if 'has host bits' in e.args[0]: raise ValidationError(self.error_messages['network']) raise ValidationError(self.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, str): value = value.strip() try: return EUI(value, version=48, dialect=mac_unix_common) except (AddrFormatError, IndexError, 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 class MACAddress8FormField(forms.Field): default_error_messages = { 'invalid': u'Enter a valid MAC address 8.', } def __init__(self, *args, **kwargs): super(MACAddress8FormField, self).__init__(*args, **kwargs) def to_python(self, value): if not value: return None if isinstance(value, EUI): return value if isinstance(value, str): value = value.strip() try: mac = EUI(value, dialect=mac_eui64) if mac.version == 64: return mac mac = mac.eui64() mac.dialect = mac_eui64 return mac except (AddrFormatError, IndexError, TypeError): raise ValidationError(self.error_messages['invalid']) def widget_attrs(self, widget): attrs = super(MACAddress8FormField, self).widget_attrs(widget) attrs.update({'maxlength': '23'}) return attrs jimfunk-django-postgresql-netfields-a6f8cbe/netfields/functions.py000066400000000000000000000046211515233030500257100ustar00rootroot00000000000000"""Postgres network address functions. https://www.postgresql.org/docs/11/functions-net.html """ from django.db.models import BooleanField, Func, IntegerField, TextField from .fields import CidrAddressField, InetAddressField, MACAddress8Field class Abbrev(Func): """Function to abbreviate field as text.""" arity = 1 function = 'ABBREV' output_field = TextField() class Broadcast(Func): """Function to extract broadcast address for network.""" arity = 1 function = 'BROADCAST' output_field = InetAddressField() class Family(Func): """Function to extract family of address; 4 for IPv4, 6 for IPv6.""" arity = 1 function = 'FAMILY' output_field = IntegerField() class Host(Func): """Function to extract IP address as text.""" arity = 1 function = 'HOST' output_field = TextField() class Hostmask(Func): """Function to construct host mask for network.""" arity = 1 function = 'HOSTMASK' output_field = InetAddressField() class Masklen(Func): """Function to extract netmask length.""" arity = 1 function = 'MASKLEN' output_field = IntegerField() class Netmask(Func): """Function to construct netmask for network.""" arity = 1 function = 'NETMASK' output_field = InetAddressField() class Network(Func): """Function to extract network part of address.""" arity = 1 function = 'NETWORK' output_field = CidrAddressField() class SetMasklen(Func): """Function to set netmask length.""" arity = 2 function = 'SET_MASKLEN' output_field = InetAddressField() class AsText(Func): """Function to extract IP address and netmask length as text.""" arity = 1 function = 'TEXT' output_field = TextField() class IsSameFamily(Func): """Function to test that addresses are from the same family.""" arity = 2 function = 'INET_SAME_FAMILY' output_field = BooleanField() class Merge(Func): """Function to calculate the smallest network which includes both of the given networks. """ arity = 2 function = 'INET_MERGE' output_field = CidrAddressField() class Trunc(Func): arity = 1 function = 'TRUNC' class Macaddr8Set7bit(Func): """Function that sets 7th bit to one, also known as modified EUI-64, for inclusion in an IPv6 address""" arity = 1 function = 'MACADDR8_SET7BIT' output_field = MACAddress8Field() jimfunk-django-postgresql-netfields-a6f8cbe/netfields/lookups.py000066400000000000000000000135571515233030500254040ustar00rootroot00000000000000import warnings from 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, list(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 hasattr(self.rhs, "resolve_expression"): return self.rhs 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 hasattr(self.rhs, "resolve_expression"): return self.rhs 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 HostMatches(AddressLookup, Lookup): lookup_name = "host" 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 "HOST(%s) = HOST(%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): warnings.warn( "min_prefixlen and max_prefixlen will be depreciated in the future; " "use prefixlen__gte and prefixlen__lte respectively", DeprecationWarning, ) 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(Transform): lookup_name = "prefixlen" def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) return "masklen(%s)" % lhs, params @property def output_field(self): return IntegerField() jimfunk-django-postgresql-netfields-a6f8cbe/netfields/mac.py000066400000000000000000000014041515233030500244340ustar00rootroot00000000000000import netaddr class mac_unix_common(netaddr.mac_eui48): """Common form of UNIX MAC address dialect class""" word_sep = ':' word_fmt = '%.2x' class mac_eui64: """A standard IEEE EUI-64 dialect class.""" #: The individual word size (in bits) of this address type. word_size = 8 #: The number of words in this address type. num_words = 64 // word_size #: The maximum integer value for an individual word in this address type. max_word = 2 ** word_size - 1 #: The separator character used between each word. word_sep = ":" #: The format string to be used when converting words to string values. word_fmt = "%.2x" #: The number base to be used when interpreting word values as integers. word_base = 16 jimfunk-django-postgresql-netfields-a6f8cbe/netfields/managers.py000066400000000000000000000011171515233030500254720ustar00rootroot00000000000000from 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) jimfunk-django-postgresql-netfields-a6f8cbe/netfields/models.py000066400000000000000000000000001515233030500251460ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/netfields/psycopg2_types.py000066400000000000000000000025361515233030500266750ustar00rootroot00000000000000import 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" class Macaddr8(Inet): """ Wrap a string for the MACADDR8 type, like Inet """ def getquoted(self): obj = psycopg2.extensions.adapt(self.addr) if hasattr(obj, 'prepare'): obj.prepare(self._conn) return obj.getquoted() + b"::macaddr8" # 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) # select typarray from pg_type where typname = 'macaddr8' -> 775 MACADDRARRAY8_OID = 775 MACADDRARRAY8 = psycopg2.extensions.new_array_type( (MACADDRARRAY8_OID,), 'MACADDRARRAY8', psycopg2.extensions.UNICODE, ) psycopg2.extensions.register_type(MACADDRARRAY8)jimfunk-django-postgresql-netfields-a6f8cbe/netfields/psycopg3_types.py000066400000000000000000000020411515233030500266650ustar00rootroot00000000000000from psycopg.adapt import Dumper, Loader from psycopg.postgres import adapters class Inet(str): def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self) class Macaddr(Inet): pass class Macaddr8(Inet): pass class _MacaddrDumper(Dumper): oid = adapters.types['macaddr'].oid def dump(self, obj): return str(obj).encode() class _MacaddrLoader(Loader): def load(self, data): if isinstance(data, memoryview): data = bytes(data) return Macaddr(data.decode()) class _Macaddr8Dumper(Dumper): oid = adapters.types['macaddr8'].oid def dump(self, obj): return str(obj).encode() class _Macaddr8Loader(Loader): def load(self, data): if isinstance(data, memoryview): data = bytes(data) return Macaddr8(data.decode()) adapters.register_loader('macaddr', _MacaddrLoader) adapters.register_loader('macaddr8', _Macaddr8Loader) adapters.register_dumper(Macaddr, _MacaddrDumper) adapters.register_dumper(Macaddr8, _Macaddr8Dumper) jimfunk-django-postgresql-netfields-a6f8cbe/netfields/rest_framework.py000066400000000000000000000060061515233030500267310ustar00rootroot00000000000000from ipaddress import ip_address, ip_interface, ip_network from netaddr import EUI from netaddr.core import AddrFormatError from rest_framework import serializers from netfields.mac import mac_unix_common, mac_eui64 from netfields import fields class InetAddressField(serializers.Field): default_error_messages = { 'invalid': 'Invalid IP address.' } def __init__(self, store_prefix=True, *args, **kwargs): self.store_prefix = store_prefix super(InetAddressField, self).__init__(*args, **kwargs) def to_representation(self, value): if value is None: return value return str(value) def to_internal_value(self, data): if data is None: return data try: if self.store_prefix: return ip_interface(data) else: return ip_address(data) except ValueError: self.fail('invalid') class CidrAddressField(serializers.Field): default_error_messages = { 'invalid': 'Invalid CIDR address.', 'network': 'Must be a network address.', } def to_representation(self, value): if value is None: return value return str(value) def to_internal_value(self, data): if data is None: return data try: return ip_network(data) except ValueError as e: if 'has host bits' in e.args[0]: self.fail('network') self.fail('invalid') class MACAddressField(serializers.Field): default_error_messages = { 'invalid': 'Invalid MAC address.' } def to_representation(self, value): if value is None: return value return str(value) def to_internal_value(self, data): if data is None: return data try: return EUI(data, version=48, dialect=mac_unix_common) except (AddrFormatError, IndexError, TypeError): self.fail('invalid') class MACAddress8Field(serializers.Field): default_error_messages = { 'invalid': 'Invalid MAC address 8.' } def to_representation(self, value): if value is None: return value return str(value) def to_internal_value(self, data): if data is None: return data try: mac = EUI(data, dialect=mac_eui64) if mac.version == 64: return mac mac = mac.eui64() mac.dialect = mac_eui64 return mac except (AddrFormatError, IndexError, TypeError): self.fail('invalid') class NetModelSerializer(serializers.ModelSerializer): pass NetModelSerializer.serializer_field_mapping[fields.InetAddressField] = InetAddressField NetModelSerializer.serializer_field_mapping[fields.CidrAddressField] = CidrAddressField NetModelSerializer.serializer_field_mapping[fields.MACAddressField] = MACAddressField NetModelSerializer.serializer_field_mapping[fields.MACAddress8Field] = MACAddress8Field jimfunk-django-postgresql-netfields-a6f8cbe/pyproject.toml000066400000000000000000000001321515233030500242560ustar00rootroot00000000000000[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" jimfunk-django-postgresql-netfields-a6f8cbe/requirements.txt000066400000000000000000000000361515233030500246310ustar00rootroot00000000000000django>=1.11 netaddr psycopg2 jimfunk-django-postgresql-netfields-a6f8cbe/setup.cfg000066400000000000000000000013371515233030500231730ustar00rootroot00000000000000[metadata] name = django-netfields version = 1.4.1 description = Django PostgreSQL netfields implementation long_description = file: README.rst url = https://github.com/jimfunk/django-postgresql-netfields author = James Oakley author_email = jfunk@funktronics.ca license = BSD classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 Topic :: Utilities [options] packages = netfields zip_safe = False install_requires = netaddr django>=1.11 python_requires = >=3.5 jimfunk-django-postgresql-netfields-a6f8cbe/setup.py000077500000000000000000000000751515233030500230650ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup setup() jimfunk-django-postgresql-netfields-a6f8cbe/test/000077500000000000000000000000001515233030500223255ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/test/__init__.py000066400000000000000000000000001515233030500244240ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/test/models.py000066400000000000000000000064661515233030500241760ustar00rootroot00000000000000from django import VERSION from django.contrib.postgres.fields import ArrayField from django.db.models import CASCADE, ForeignKey, Model from netfields import ( CidrAddressField, InetAddressField, MACAddress8Field, 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 MAC8TestModel(Model): field = MACAddress8Field(null=True) objects = NetManager() class Meta: db_table = "mac8" 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 MAC8ArrayTestModel(Model): field = ArrayField(MACAddress8Field(), blank=True, null=True) class Meta: db_table = "mac8array" class AggregateTestModel(Model): network = CidrAddressField(blank=True, null=True, default=None) inet = InetAddressField(blank=True, null=True, default=None) class AggregateTestChildModel(Model): parent = ForeignKey( "AggregateTestModel", related_name="children", on_delete=CASCADE, ) network = CidrAddressField() inet = InetAddressField() if VERSION >= (5, 1): from django.db.models import F, Q, CheckConstraint class ConstraintModel(Model): network = CidrAddressField() inet = InetAddressField() class Meta: constraints = ( CheckConstraint( condition=Q(network__net_contains=F("inet")), name="inet_contained", ), ) elif VERSION >= (4, 1): from django.db.models import F, Q, CheckConstraint class ConstraintModel(Model): network = CidrAddressField() inet = InetAddressField() class Meta: constraints = ( CheckConstraint( check=Q(network__net_contains=F("inet")), name="inet_contained", ), ) jimfunk-django-postgresql-netfields-a6f8cbe/test/tests/000077500000000000000000000000001515233030500234675ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/test/tests/__init__.py000066400000000000000000000000001515233030500255660ustar00rootroot00000000000000jimfunk-django-postgresql-netfields-a6f8cbe/test/tests/test_form_fields.py000066400000000000000000000240361515233030500273760ustar00rootroot00000000000000from 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, MAC8TestModel ) from netfields.mac import mac_eui64, 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()) self.assertEqual(form.cleaned_data['field'], ip_address('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_prefix_invalid(self): instance = NoPrefixInetTestModel.objects.create(field='10.1.2.3/24') form = self.form_class({'field': '10.1.2.4/24'}, instance=instance) self.assertFalse(form.is_valid()) 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_address('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_prefix_invalid(self): instance = NoPrefixInetTestModel.objects.create(field='2001:0:1::2/64') form = self.form_class({'field': '2001:0:1::3/64'}, instance=instance) self.assertFalse(form.is_valid()) 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_eui64(self): form = MacAddressTestModelForm({'field': '00-AA-2B-C3-DD-44-EE-55'}) self.assertFalse(form.is_valid()) def test_invalid(self): form = MacAddressTestModelForm({'field': 'notvalid'}) self.assertFalse(form.is_valid()) class MacAddress8TestModelForm(ModelForm): class Meta: model = MAC8TestModel exclude = [] class TestMacAddress8FormField(TestCase): def setUp(self): self.mac = EUI('00:aa:2b:c3:dd:44:55:ff', dialect=mac_eui64) def test_unix(self): form = MacAddress8TestModelForm({'field': '0:AA:2b:c3:dd:44:55:FF'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_unix_common(self): form = MacAddress8TestModelForm({'field': '00:aa:2b:c3:dd:44:55:ff'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_eui64(self): form = MacAddress8TestModelForm({'field': '00-AA-2B-C3-DD-44-55-FF'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_cisco(self): form = MacAddress8TestModelForm({'field': '00aa.2bc3.dd44.55ff'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_16bit_colon(self): form = MacAddress8TestModelForm({'field': '00aa:2bc3:dd44:55ff'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_16bit_hyphen(self): form = MacAddress8TestModelForm({'field': '00aa-2bc3-dd44-55ff'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_strip(self): form = MacAddress8TestModelForm({'field': ' 00:aa:2b:c3:dd:44:55:ff '}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], self.mac) def test_eui48(self): form = MacAddress8TestModelForm({'field': '00:aa:2b:c3:dd:44'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['field'], EUI('00:aa:2b:ff:fe:c3:dd:44', dialect=mac_eui64)) def test_invalid(self): form = MacAddress8TestModelForm({'field': 'notvalid'}) self.assertFalse(form.is_valid()) jimfunk-django-postgresql-netfields-a6f8cbe/test/tests/test_functions.py000066400000000000000000000237511515233030500271200ustar00rootroot00000000000000from django import VERSION from ipaddress import ip_interface, ip_network from netaddr import EUI from django.db.models import Case, F, When from django.test import TestCase from unittest import skipIf from netfields.functions import ( Abbrev, Broadcast, Family, Host, Hostmask, Macaddr8Set7bit, Masklen, Netmask, Network, SetMasklen, AsText, IsSameFamily, Merge, Trunc ) from test.models import ( AggregateTestChildModel, AggregateTestModel, CidrTestModel, InetTestModel, MACTestModel, MAC8TestModel ) class TestInetFieldFunctions(TestCase): def setUp(self): InetTestModel.objects.create(field='10.1.0.1/16') InetTestModel.objects.create(field='2001:4f8:3:ba::1/64') def test_abbreviate(self): qs = InetTestModel.objects.annotate(abbrv=Abbrev(F('field'))) self.assertEqual(qs[0].abbrv, '10.1.0.1/16') self.assertEqual(qs[1].abbrv, '2001:4f8:3:ba::1/64') def test_broadcast(self): qs = InetTestModel.objects.annotate(broadcast=Broadcast(F('field'))) self.assertEqual(qs[0].broadcast, ip_interface('10.1.255.255/16')) self.assertEqual(qs[1].broadcast, ip_interface('2001:4f8:3:ba:ffff:ffff:ffff:ffff/64')) def test_family(self): qs = InetTestModel.objects.annotate(family=Family(F('field'))) self.assertEqual(qs[0].family, 4) self.assertEqual(qs[1].family, 6) def test_host(self): qs = InetTestModel.objects.annotate(host=Host(F('field'))) self.assertEqual(qs[0].host, '10.1.0.1') self.assertEqual(qs[1].host, '2001:4f8:3:ba::1') def test_hostmask(self): qs = InetTestModel.objects.annotate(hostmask=Hostmask(F('field'))) self.assertEqual(qs[0].hostmask, ip_interface('0.0.255.255')) self.assertEqual(qs[1].hostmask, ip_interface('::ffff:ffff:ffff:ffff')) def test_masklen(self): qs = InetTestModel.objects.annotate(masklen=Masklen(F('field'))) self.assertEqual(qs[0].masklen, 16) self.assertEqual(qs[1].masklen, 64) def test_netmask(self): qs = InetTestModel.objects.annotate(netmask=Netmask(F('field'))) self.assertEqual(qs[0].netmask, ip_interface('255.255.0.0')) self.assertEqual(qs[1].netmask, ip_interface('ffff:ffff:ffff:ffff::')) def test_network(self): qs = InetTestModel.objects.annotate(network=Network(F('field'))) self.assertEqual(qs[0].network, ip_network('10.1.0.0/16')) self.assertEqual(qs[1].network, ip_network('2001:4f8:3:ba::/64')) def test_set_masklen(self): ( InetTestModel.objects .annotate(family=Family(F('field'))) .update( field=Case( When(family=4, then=SetMasklen(F('field'), 24)), When(family=6, then=SetMasklen(F('field'), 120)) ) ) ) qs = InetTestModel.objects.all() self.assertEqual(qs[0].field, ip_interface('10.1.0.1/24')) self.assertEqual(qs[1].field, ip_interface('2001:4f8:3:ba::1/120')) def test_as_text(self): qs = InetTestModel.objects.annotate(text=AsText(F('field'))) self.assertEqual(qs[0].text, '10.1.0.1/16') self.assertEqual(qs[1].text, '2001:4f8:3:ba::1/64') def test_is_same_family(self): parent = AggregateTestModel.objects.create(inet='0.0.0.0/0') AggregateTestChildModel.objects.create( parent=parent, inet='10.1.0.1/16', network='10.1.0.0/16' ) AggregateTestChildModel.objects.create( parent=parent, inet='2001:4f8:3:ba::1/64', network='2001:4f8:3:ba::/64' ) qs = ( AggregateTestChildModel.objects.annotate( is_same_family=IsSameFamily(F('inet'), F('parent__inet')) ) .order_by('id') ) self.assertEqual(qs[0].is_same_family, True) self.assertEqual(qs[1].is_same_family, False) def test_merge(self): parent = AggregateTestModel.objects.create(inet='10.0.0.0/24') AggregateTestChildModel.objects.create( parent=parent, inet='10.0.1.0/24', network='10.0.0.0/23' ) parent = AggregateTestModel.objects.create(inet='2001:4f8:3:ba::/64') AggregateTestChildModel.objects.create( parent=parent, inet='2001:4f8:3:bb::/64', network='2001:4f8:3:ba::/63' ) qs = ( AggregateTestChildModel.objects.annotate( merged=Merge(F('inet'), F('parent__inet')) ) ) self.assertEqual(qs[0].merged, qs[0].network) self.assertEqual(qs[1].merged, qs[1].network) class TestCidrFieldFunctions(TestCase): def setUp(self): CidrTestModel.objects.create(field='10.1.0.0/16') CidrTestModel.objects.create(field='2001:4f8:3:ba::/64') def test_abbreviate(self): qs = CidrTestModel.objects.annotate(abbrv=Abbrev(F('field'))) self.assertEqual(qs[0].abbrv, '10.1/16') self.assertEqual(qs[1].abbrv, '2001:4f8:3:ba/64') def test_broadcast(self): qs = CidrTestModel.objects.annotate(broadcast=Broadcast(F('field'))) self.assertEqual(qs[0].broadcast, ip_interface('10.1.255.255/16')) self.assertEqual(qs[1].broadcast, ip_interface('2001:4f8:3:ba:ffff:ffff:ffff:ffff/64')) def test_family(self): qs = CidrTestModel.objects.annotate(family=Family(F('field'))) self.assertEqual(qs[0].family, 4) self.assertEqual(qs[1].family, 6) def test_host(self): qs = CidrTestModel.objects.annotate(host=Host(F('field'))) self.assertEqual(qs[0].host, '10.1.0.0') self.assertEqual(qs[1].host, '2001:4f8:3:ba::') def test_hostmask(self): qs = CidrTestModel.objects.annotate(hostmask=Hostmask(F('field'))) self.assertEqual(qs[0].hostmask, ip_interface('0.0.255.255')) self.assertEqual(qs[1].hostmask, ip_interface('::ffff:ffff:ffff:ffff')) def test_masklen(self): qs = CidrTestModel.objects.annotate(masklen=Masklen(F('field'))) self.assertEqual(qs[0].masklen, 16) self.assertEqual(qs[1].masklen, 64) def test_netmask(self): qs = CidrTestModel.objects.annotate(netmask=Netmask(F('field'))) self.assertEqual(qs[0].netmask, ip_interface('255.255.0.0')) self.assertEqual(qs[1].netmask, ip_interface('ffff:ffff:ffff:ffff::')) def test_network(self): qs = CidrTestModel.objects.annotate(network=Network(F('field'))) self.assertEqual(qs[0].network, ip_network('10.1.0.0/16')) self.assertEqual(qs[1].network, ip_network('2001:4f8:3:ba::/64')) def test_set_masklen(self): ( CidrTestModel.objects .annotate(family=Family(F('field'))) .update( field=Case( When(family=4, then=SetMasklen(F('field'), 24)), When(family=6, then=SetMasklen(F('field'), 120)) ) ) ) qs = CidrTestModel.objects.all() self.assertEqual(qs[0].field, ip_network('10.1.0.0/24')) self.assertEqual(qs[1].field, ip_network('2001:4f8:3:ba::/120')) def test_as_text(self): qs = CidrTestModel.objects.annotate(text=AsText(F('field'))) self.assertEqual(qs[0].text, '10.1.0.0/16') self.assertEqual(qs[1].text, '2001:4f8:3:ba::/64') def test_is_same_family(self): parent = AggregateTestModel.objects.create(network='0.0.0.0/0') AggregateTestChildModel.objects.create( parent=parent, inet= '10.1.0.1/16', network='10.1.0.0/16' ) AggregateTestChildModel.objects.create( parent=parent, inet='2001:4f8:3:ba::1/64', network='2001:4f8:3:ba::/64' ) qs = ( AggregateTestChildModel.objects.annotate( is_same_family=IsSameFamily(F('network'), F('parent__network')) ) .order_by('id') ) self.assertEqual(qs[0].is_same_family, True) self.assertEqual(qs[1].is_same_family, False) def test_merge(self): parent = AggregateTestModel.objects.create(network='10.0.0.0/24') AggregateTestChildModel.objects.create( parent=parent, inet='10.0.1.0/24', network='10.0.0.0/23' ) parent = AggregateTestModel.objects.create(network='2001:4f8:3:ba::/64') AggregateTestChildModel.objects.create( parent=parent, inet='2001:4f8:3:bb::/64', network='2001:4f8:3:ba::/63' ) qs = ( AggregateTestChildModel.objects.annotate( merged=Merge(F('network'), F('parent__network')) ) ) self.assertEqual(qs[0].merged, qs[0].network) self.assertEqual(qs[1].merged, qs[1].network) @skipIf(VERSION < (2, 0), 'Django unable to resolve type of num_ips to be IntegerField until 2.0.') def test_read_me_example(self): qs = ( CidrTestModel.objects.annotate( family=Family(F('field')), num_ips=2 ** (32 - Masklen(F('field'))), ) .filter(family=4) ) self.assertEqual(qs[0].num_ips, 65536) class TestMacFieldFunctions(TestCase): def setUp(self): MACTestModel.objects.create(field='88:bb:cc:dd:ee:ff') def test_trunc(self): qs = MACTestModel.objects.annotate(trunc=Trunc(F('field'))) self.assertEqual(qs[0].trunc, EUI('88:bb:cc:00:00:00')) def test_macaddr8_to7bit(self): qs = MACTestModel.objects.annotate(eui64=Macaddr8Set7bit(F('field'))) self.assertEqual(qs[0].eui64, EUI('8a:bb:cc:ff:fe:dd:ee:ff')) class TestMac8FieldFunctions(TestCase): def setUp(self): MAC8TestModel.objects.create(field='88:99:aa:bb:cc:dd:ee:ff') def test_trunc(self): qs = MAC8TestModel.objects.annotate(trunc=Trunc(F('field'))) self.assertEqual(qs[0].trunc, EUI('88:99:aa:00:00:00:00:00')) def test_macaddr8_to7bit(self): qs = MAC8TestModel.objects.annotate(eui64=Macaddr8Set7bit(F('field'))) self.assertEqual(qs[0].eui64, EUI('8a:99:aa:bb:cc:dd:ee:ff')) jimfunk-django-postgresql-netfields-a6f8cbe/test/tests/test_rest_framework_fields.py000066400000000000000000000113521515233030500314620ustar00rootroot00000000000000from rest_framework import serializers import sys import 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."]) 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."]) def test_network_validation_cidr_field(self): class TestSerializer(serializers.Serializer): cidr = fields.CidrAddressField() address = '10.0.0.1/24' serializer = TestSerializer(data={'cidr': address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertEqual(e.exception.detail['cidr'], ["Must be a network address."]) def test_validation_mac_field(self): class TestSerializer(serializers.Serializer): mac = fields.MACAddressField() for invalid_address in ("de:", {"not": "a mac"}): with self.subTest(invalid_address=invalid_address): serializer = TestSerializer(data={'mac': invalid_address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertEqual(e.exception.detail['mac'], ["Invalid MAC address."]) def test_validation_mac8_field(self): class TestSerializer(serializers.Serializer): mac8 = fields.MACAddress8Field() for invalid_address in ("de:", {"not": "a mac8"}): with self.subTest(invalid_address=invalid_address): serializer = TestSerializer(data={'mac8': invalid_address}) with self.assertRaises(serializers.ValidationError) as e: serializer.is_valid(raise_exception=True) self.assertEqual(e.exception.detail['mac8'], ["Invalid MAC address 8."]) def test_inet_validation_additional_validators(self): def validate(value): raise serializers.ValidationError('Invalid.') class TestSerializer(serializers.Serializer): ip = fields.InetAddressField(validators=[validate]) address = '1.2.3.4/24' 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.']) def test_cidr_validation_additional_validators(self): def validate(value): raise serializers.ValidationError('Invalid.') class TestSerializer(serializers.Serializer): ip = fields.CidrAddressField(validators=[validate]) address = '1.2.3.0/24' 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.']) def test_mac_validation_additional_validators(self): def validate(value): raise serializers.ValidationError('Invalid.') class TestSerializer(serializers.Serializer): ip = fields.MACAddressField(validators=[validate]) address = '01:23:45:67:89:ab' 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.']) def test_mac8_validation_additional_validators(self): def validate(value): raise serializers.ValidationError('Invalid.') class TestSerializer(serializers.Serializer): ip = fields.MACAddress8Field(validators=[validate]) address = '01:23:45:67:89:ab:bc:dc' 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.']) jimfunk-django-postgresql-netfields-a6f8cbe/test/tests/test_sql_fields.py000066400000000000000000000646011515233030500272340ustar00rootroot00000000000000import warnings import django from django import VERSION as DJANGO_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 import sys from django.db import IntegrityError from django.db.models import F from django.core.exceptions import EmptyResultSet, FieldError from django.test import TestCase from unittest import skipIf from test.models import ( CidrArrayTestModel, CidrTestModel, InetArrayTestModel, InetTestModel, NullCidrTestModel, NullInetTestModel, UniqueInetTestModel, UniqueCidrTestModel, NoPrefixInetTestModel, MACArrayTestModel, MAC8ArrayTestModel, MACTestModel, MAC8TestModel, AggregateTestModel, AggregateTestChildModel ) PYTHON_VERSION = (sys.version_info.major, sys.version_info.minor) 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): if DJANGO_VERSION >= (2, 0): expected = FieldError else: expected = NotImplementedError with self.assertRaises(expected): 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' ) def test_host_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__host="10.0.0.1"), self.select + 'WHERE HOST("table"."field") = HOST(%s)' ) def test_prefixlen_exact_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen='16'), self.select + 'WHERE MASKLEN("table"."field") = %s' ) def test_prefixlen_in_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen__in=['16', '24']), self.select + 'WHERE MASKLEN("table"."field") IN (%s, %s)' ) def test_prefixlen_in_single_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen__in=['16']), self.select + 'WHERE MASKLEN("table"."field") IN (%s)' ) def test_prefixlen_in_empty_lookup_sql(self): with self.assertRaises(EmptyResultSet): self.qs.filter(field__prefixlen__in=[]).query.get_compiler(self.qs.db).as_sql() def test_prefixlen_gt_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen__gt="16"), self.select + 'WHERE MASKLEN("table"."field") > %s' ) def test_prefixlen_gte_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen__gte="16"), self.select + 'WHERE MASKLEN("table"."field") >= %s' ) def test_prefixlen_lt_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen__lt="16"), self.select + 'WHERE MASKLEN("table"."field") < %s' ) def test_prefixlen_lte_lookup_sql(self): self.assertSqlEquals( self.qs.filter(field__prefixlen__lte="16"), self.select + 'WHERE MASKLEN("table"."field") <= %s' ) def test_query_filter_f_expression(self): self.model.objects.filter(field=F('field')) def test_query_filter_subquery(self): from django.db.models import OuterRef, Subquery self.model.objects.annotate( samefield=Subquery( self.model.objects .filter( field=OuterRef('field') ) .values('field')[:1] ) ) 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): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") self.assertSqlEquals( self.qs.filter(field__max_prefixlen='16'), self.select + 'WHERE masklen("table"."field") <= %s' ) assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) def test_min_prefixlen(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") self.assertSqlEquals( self.qs.filter(field__min_prefixlen='16'), self.select + 'WHERE masklen("table"."field") >= %s' ) assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) 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') def test_host(self): instance = self.model.objects.create(field='10.1.2.3/24') instance = self.model.objects.get(field__host='10.1.2.3') self.assertEqual(str(instance.field), '10.1.2.3/24') instance = self.model.objects.get(field__host='10.1.2.3/27') self.assertEqual(str(instance.field), '10.1.2.3/24') @skipIf(PYTHON_VERSION < (3, 9), 'Scopes were added in Python 3.9') def test_ipv6_strip_scope(self): instance = self.model.objects.create(field='2001:db8::1%foo/64') instance = self.model.objects.get(pk=instance.pk) self.assertEqual(str(instance.field), '2001:db8::1/64') 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) def test_net_contained_network(self): self.model.objects.create(field='10.1.2.1') self.model.objects.create(field='10.1.3.1') query = self.model.objects.filter(field__net_contained='10.1.2.0/24') self.assertEqual(query.count(), 1) self.assertEqual(query[0].field, ip_address('10.1.2.1')) @skipIf(PYTHON_VERSION < (3, 9), 'Scopes were added in Python 3.9') def test_ipv6_strip_scope(self): instance = self.model.objects.create(field='2001:db8::1%foo') instance = self.model.objects.get(pk=instance.pk) self.assertEqual(str(instance.field), '2001:db8::1') 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) def test_host(self): instance = self.model.objects.create(field='10.1.2.0/24') instance = self.model.objects.get(field__host='10.1.2.0') self.assertEqual(str(instance.field), '10.1.2.0/24') instance = self.model.objects.get(field__host='10.1.2.0/27') self.assertEqual(str(instance.field), '10.1.2.0/24') 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 = NotImplemented value2 = NotImplemented value3 = NotImplemented 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' ) def test_query_filter_f_expression(self): self.model.objects.filter(field=F('field')) def test_query_filter_subquery(self): from django.db.models import OuterRef, Subquery self.model.objects.annotate( samefield=Subquery( self.model.objects .filter( field=OuterRef('field') ) .values('field')[:1] ) ) class TestMacAddressField(BaseMacTestCase, TestCase): value1 = '00:aa:2b:c3:dd:44' value2 = '00:aa:2b:c3:dd:45' value3 = '00:aa:2b:c3:dd:ff' 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 TestMacAddress8Field(BaseMacTestCase, TestCase): value1 = '00:aa:2b:c3:dd:44:56:74' value2 = '00:aa:2b:c3:dd:45:98:63' value3 = '00:aa:2b:c3:dd:ff:a5:b6' def setUp(self): self.model = MAC8TestModel self.qs = self.model.objects.all() self.table = 'mac8' 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:55:66') 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() def test_save_with_null_item(self): InetArrayTestModel(field=['10.1.1.1', None]).save() 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() def test_save_with_null_item(self): CidrArrayTestModel(field=['10.1.1.0/24', None]).save() 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() def test_save_with_null_item(self): MACArrayTestModel(field=['00:aa:2b:c3:dd:44', None]).save() 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 TestMACAddress8FieldArray(TestCase): def test_save_null(self): MAC8ArrayTestModel().save() def test_save_single_item(self): MAC8ArrayTestModel(field=['00:aa:2b:c3:dd:44:55:ff']).save() def test_save_multiple_items(self): MAC8ArrayTestModel(field=['00:aa:2b:c3:dd:44:55:ff', '00:aa:2b:c3:dd:45:7e:6b']).save() def test_save_with_null_item(self): MAC8ArrayTestModel(field=['00:aa:2b:c3:dd:44:55:ff', None]).save() def test_retrieves_eui_type(self): instance = MAC8ArrayTestModel(field=['00:aa:2b:c3:dd:44:55:67']) instance.save() instance = MAC8ArrayTestModel.objects.get(id=instance.id) self.assertEqual(instance.field, [EUI('00:aa:2b:c3:dd:44:55:67')]) self.assertIsInstance(instance.field[0], EUI) class TestAggregate(TestCase): 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, [None]) AggregateTestChildModel.objects.create(parent=parent, network=network, inet=inet) self.assertEqual(inet_qs[0].agg_inet, [inet]) 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, [None]) AggregateTestChildModel.objects.create(parent=parent, network=network, inet=inet) self.assertEqual(network_qs[0].agg_network, [network]) class TestConstraints(TestCase): @skipIf(DJANGO_VERSION < (4, 1), 'Check constraint validation is supported from django 4.1 onwards') def test_check_constraint(self): from test.models import ConstraintModel inet = IPv4Interface('10.10.10.20/32') network = IPv4Network('10.10.10.0/24') model = ConstraintModel(inet=inet, network=network) model.full_clean() model.save() jimfunk-django-postgresql-netfields-a6f8cbe/testsettings.py000066400000000000000000000007701515233030500244640ustar00rootroot00000000000000import os DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "netfields", } } INSTALLED_APPS = ( "django.contrib.postgres", "netfields", "test", ) MIDDLEWARE_CLASSES = ( "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ) SECRET_KEY = "notimportant" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" jimfunk-django-postgresql-netfields-a6f8cbe/tox.ini000066400000000000000000000217411515233030500226660ustar00rootroot00000000000000[tox] envlist= py35-django111, py36-django111, py37-django111, py38-django111, py39-django111, py35-django22, py36-django22, py37-django22, py38-django22, py39-django22, py36-django30, py37-django30, py38-django30, py39-django30, py36-django31, py37-django31, py38-django31, py39-django31, py36-django32, py37-django32, py38-django32, py39-django32, py310-django32, py38-django40, py39-django40, py310-django40, py38-django41, py39-django41, py310-django41, py311-django41, py38-django42, py39-django42, py310-django42, py311-django42, py311-django42-psycopg3, py312-django42, py312-django42-psycopg3, py313-django42, py313-django42-psycopg3, py310-django51, py311-django51, py311-django51-psycopg3, py312-django51, py312-django51-psycopg3, py313-django51, py313-django51-psycopg3, py310-django52, py311-django52, py311-django52-psycopg3, py312-django52, py312-django52-psycopg3, py313-django52, py313-django52-psycopg3, py312-django60, py312-django60-psycopg3, py313-django60, py313-django60-psycopg3, py314-django60, py314-django60-psycopg3, [testenv] commands= python manage.py test {posargs} # Build configurations... [testenv:py35-django111] basepython=python3.5 deps= django>=1.11,<1.12 netaddr psycopg2-binary djangorestframework<3.10 [testenv:py36-django111] basepython=python3.6 deps= django>=1.11,<1.12 netaddr psycopg2-binary djangorestframework<3.10 [testenv:py37-django111] basepython=python3.7 deps= django>=1.11,<1.12 netaddr psycopg2-binary djangorestframework<3.10 [testenv:py38-django111] basepython=python3.8 deps= django>=1.11,<1.12 netaddr psycopg2-binary djangorestframework<3.10 [testenv:py39-django111] basepython=python3.9 deps= django>=1.11,<1.12 netaddr psycopg2-binary djangorestframework<3.10 [testenv:py35-django22] basepython=python3.5 deps= django>=2.2,<3.0 netaddr psycopg2-binary djangorestframework [testenv:py36-django22] basepython=python3.6 deps= django>=2.2,<3.0 netaddr psycopg2-binary djangorestframework [testenv:py37-django22] basepython=python3.7 deps= django>=2.2,<3.0 netaddr psycopg2-binary djangorestframework [testenv:py38-django22] basepython=python3.8 deps= django>=2.2,<3.0 netaddr psycopg2-binary djangorestframework [testenv:py39-django22] basepython=python3.9 deps= django>=2.2,<3.0 netaddr psycopg2-binary djangorestframework [testenv:py36-django30] basepython=python3.6 deps= django>=3.0,<3.1 netaddr psycopg2-binary djangorestframework [testenv:py37-django30] basepython=python3.7 deps= django>=3.0,<3.1 netaddr psycopg2-binary djangorestframework [testenv:py38-django30] basepython=python3.8 deps= django>=3.0,<3.1 netaddr psycopg2-binary djangorestframework [testenv:py39-django30] basepython=python3.9 deps= django>=3.0,<3.1 netaddr psycopg2-binary djangorestframework [testenv:py36-django31] basepython=python3.6 deps= django>=3.1,<3.2 netaddr psycopg2-binary djangorestframework [testenv:py37-django31] basepython=python3.7 deps= django>=3.1,<3.2 netaddr psycopg2-binary djangorestframework [testenv:py38-django31] basepython=python3.8 deps= django>=3.1,<3.2 netaddr psycopg2-binary djangorestframework [testenv:py39-django31] basepython=python3.9 deps= django>=3.1,<3.2 netaddr psycopg2-binary djangorestframework [testenv:py36-django32] basepython=python3.6 deps= django>=3.2,<3.3 netaddr psycopg2-binary djangorestframework [testenv:py37-django32] basepython=python3.7 deps= django>=3.2,<3.3 netaddr psycopg2-binary djangorestframework [testenv:py38-django32] basepython=python3.8 deps= django>=3.2,<3.3 netaddr psycopg2-binary djangorestframework [testenv:py39-django32] basepython=python3.9 deps= django>=3.2,<3.3 netaddr psycopg2-binary djangorestframework [testenv:py310-django32] basepython=python3.10 deps= django>=3.2,<3.3 netaddr psycopg2-binary djangorestframework [testenv:py38-django40] basepython=python3.8 deps= django>=4.0,<4.1 netaddr psycopg2-binary djangorestframework [testenv:py39-django40] basepython=python3.9 deps= django>=4.0,<4.1 netaddr psycopg2-binary djangorestframework [testenv:py310-django40] basepython=python3.10 deps= django>=4.0,<4.1 netaddr psycopg2-binary djangorestframework [testenv:py38-django41] basepython=python3.8 deps= django>=4.1,<4.2 netaddr psycopg2-binary djangorestframework [testenv:py39-django41] basepython=python3.9 deps= django>=4.1,<4.2 netaddr psycopg2-binary djangorestframework [testenv:py310-django41] basepython=python3.10 deps= django>=4.1,<4.2 netaddr psycopg2-binary djangorestframework [testenv:py311-django41] basepython=python3.11 deps= django>=4.1,<4.2 netaddr psycopg2-binary djangorestframework [testenv:py38-django42] basepython=python3.8 deps= django>=4.2,<4.3 netaddr psycopg2-binary djangorestframework [testenv:py39-django42] basepython=python3.9 deps= django>=4.2,<4.3 netaddr psycopg2-binary djangorestframework [testenv:py310-django42] basepython=python3.10 deps= django>=4.2,<4.3 netaddr psycopg2-binary djangorestframework [testenv:py311-django42] basepython=python3.11 deps= django>=4.2,<4.3 netaddr psycopg2-binary djangorestframework [testenv:py311-django42-psycopg3] basepython=python3.11 deps= django>=4.2,<4.3 netaddr psycopg[binary] djangorestframework [testenv:py312-django42] basepython=python3.12 deps= django>=4.2,<4.3 netaddr psycopg2-binary djangorestframework [testenv:py312-django42-psycopg3] basepython=python3.12 deps= django>=4.2,<4.3 netaddr psycopg[binary] djangorestframework [testenv:py313-django42] basepython=python3.13 deps= django>=4.2,<4.3 netaddr psycopg2-binary djangorestframework [testenv:py313-django42-psycopg3] basepython=python3.13 deps= django>=4.2,<4.3 netaddr psycopg[binary] djangorestframework [testenv:py310-django51] basepython=python3.10 deps= django>=5.1,<5.2 netaddr psycopg2-binary djangorestframework [testenv:py311-django51] basepython=python3.11 deps= django>=5.1,<5.2 netaddr psycopg2-binary djangorestframework [testenv:py311-django51-psycopg3] basepython=python3.11 deps= django>=5.1,<5.2 netaddr psycopg[binary] djangorestframework [testenv:py312-django51] basepython=python3.12 deps= django>=5.1,<5.2 netaddr psycopg2-binary djangorestframework [testenv:py312-django51-psycopg3] basepython=python3.12 deps= django>=5.1,<5.2 netaddr psycopg[binary] djangorestframework [testenv:py313-django51] basepython=python3.13 deps= django>=5.1,<5.2 netaddr psycopg2-binary djangorestframework [testenv:py313-django51-psycopg3] basepython=python3.13 deps= django>=5.1,<5.2 netaddr psycopg[binary] djangorestframework [testenv:py310-django52] basepython=python3.10 deps= django>=5.2,<5.3 netaddr psycopg2-binary djangorestframework [testenv:py311-django52] basepython=python3.11 deps= django>=5.2,<5.3 netaddr psycopg2-binary djangorestframework [testenv:py311-django52-psycopg3] basepython=python3.11 deps= django>=5.2,<5.3 netaddr psycopg[binary] djangorestframework [testenv:py312-django52] basepython=python3.12 deps= django>=5.2,<5.3 netaddr psycopg2-binary djangorestframework [testenv:py312-django52-psycopg3] basepython=python3.12 deps= django>=5.2,<5.3 netaddr psycopg[binary] djangorestframework [testenv:py313-django52] basepython=python3.13 deps= django>=5.2,<5.3 netaddr psycopg2-binary djangorestframework [testenv:py313-django52-psycopg3] basepython=python3.13 deps= django>=5.2,<5.3 netaddr psycopg[binary] djangorestframework [testenv:py312-django60] basepython=python3.12 deps= django>=6.0,<6.1 netaddr psycopg2-binary djangorestframework [testenv:py312-django60-psycopg3] basepython=python3.12 deps= django>=6.0,<6.1 netaddr psycopg[binary] djangorestframework [testenv:py313-django60] basepython=python3.13 deps= django>=6.0,<6.1 netaddr psycopg2-binary djangorestframework [testenv:py313-django60-psycopg3] basepython=python3.13 deps= django>=6.0,<6.1 netaddr psycopg[binary] djangorestframework [testenv:py314-django60] basepython=python3.14 deps= django>=6.0,<6.1 netaddr psycopg2-binary djangorestframework [testenv:py314-django60-psycopg3] basepython=python3.14 deps= django>=6.0,<6.1 netaddr psycopg[binary] djangorestframework