#37339 new Bug

`GenericIPAddressField` cannot be assigned `ipaddress.IPv4Address/IPv6Address` values

Reported by: Thibaut Decombe Owned by:
Component: Database layer (models, ORM) Version: 6.1
Severity: Normal Keywords:
Cc: Thibaut Decombe Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description

Assigning a value from the stdlib ipaddress module to a GenericIPAddressField fails on save:

>>> obj.ip = ipaddress.ip_address('80.15.186.250') # Is of type IPv4Address('80.15.186.250')
>>> obj.save()
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/base.py", line 902, in save
    self.save_base(
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/base.py", line 1008, in save_base
    updated = self._save_table(
              ^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/base.py", line 1138, in _save_table
    updated = self._do_update(
              ^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/base.py", line 1203, in _do_update
    return filtered._update(values) > 0
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/query.py", line 1288, in _update
    return query.get_compiler(self.db).execute_sql(ROW_COUNT)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 2060, in execute_sql
    row_count = super().execute_sql(result_type)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1610, in execute_sql
    sql, params = self.as_sql()
                  ^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/backends/mysql/compiler.py", line 49, in as_sql
    update_query, update_params = super().as_sql()
                                  ^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 2023, in as_sql
    val = field.get_db_prep_save(val, connection=self.connection)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/fields/__init__.py", line 1012, in get_db_prep_save
    return self.get_db_prep_value(value, connection=connection, prepared=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/fields/__init__.py", line 2284, in get_db_prep_value
    value = self.get_prep_value(value)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/fields/__init__.py", line 2291, in get_prep_value
    if value and ":" in value:
                 ^^^^^^^^^^^^
TypeError: argument of type 'IPv4Address' is not iterable

Since ipaddress is the stdlib's representation of an IP address, and since it is what validate_ipv4_address()/validate_ipv6_address() already use internally, it is a natural type to hold an address in before assigning it to a model.

This looks like an oversight rather than a deliberate restriction, since the field already accepts these values elsewhere, to_python() coerces non-strings, while get_prep_value() does not. Which means it's ok to create using this tpye, but not update

>>> GenericIPAddressField().to_python(ipaddress.ip_address('80.15.186.250'))
'80.15.186.250'
>>> Bank(ip= IPv4Address('80.15.186.250')).save()                 # OK
>>> Bank.objects.update(ip=IPv4Address('80.15.186.250'))  # OK
>>> b = Bank.objects.last()
>>> b.ip  = IPv4Address('80.15.186.250')
>>> b.save()         
  File "/home/thibaut/Desktop/.venv/lib/python3.11/site-packages/django/db/models/fields/__init__.py", line 2291, in get_prep_value
    if value and ":" in value:
                 ^^^^^^^^^^^^
TypeError: argument of type 'IPv4Address' is not iterable
def get_prep_value(self, value):
    value = super().get_prep_value(value)
    if value is None:
        return None
    if value and ":" in value:     # TypeError for any non-str value
        ...
    return str(value)              # unreachable for non-str values

The trailing str(value) suggests non-string values were meant to be handled, but the membership test runs first.
Coercing before the colon check (or delegating toto_python(), as CharField does) is enough.

I'd be happy to submit a fix

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top