Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug when trying to overload natural_key_field_names with nested related fields #3898

Closed
jathanism opened this issue Jun 12, 2023 · 1 comment · Fixed by #3903
Closed

Bug when trying to overload natural_key_field_names with nested related fields #3898

jathanism opened this issue Jun 12, 2023 · 1 comment · Fixed by #3903
Assignees
Labels
type: bug Something isn't working as expected
Milestone

Comments

@jathanism
Copy link
Contributor

jathanism commented Jun 12, 2023

Environment

  • Nautobot version (Docker tag too if applicable): 2.0.0b1
  • Python version: 3.9
  • Database platform, version: n/a
  • Middleware(s): n/a

Steps to Reproduce

  1. Replace IPAddress.natural_key_field_names with ["parent__namespace__name", "host"]
  2. Get an instance of an IPAddress
  3. Attempt to call IPAddress.objects.get_by_natural_key(*instance.natural_key()) to perform a symmetric lookup

Expected Behavior

Should return the same instance that was used to make the call.

Observed Behavior

A FieldDoesNotExist error is raised:

In [1]: instance = IPAddress.objects.first()

In [2]: instance.natural_key()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/Library/Caches/pypoetry/virtualenvs/nautobot-b2ttWva6-py3.10/lib/python3.10/site-packages/django/db/models/options.py:608, in Options.get_field(self, field_name)
    605 try:
    606     # Retrieve field instance by name from cached or just-computed
    607     # field map.
--> 608     return self.fields_map[field_name]
    609 except KeyError:

KeyError: 'parent__namespace__name'

During handling of the above exception, another exception occurred:

FieldDoesNotExist                         Traceback (most recent call last)
Cell In[2], line 1
----> 1 instance.natural_key()

File ~/sandbox/src/nautobot/nautobot/core/models/__init__.py:90, in BaseModel.natural_key(self)
     83 """
     84 Smarter default implementation of natural key construction.
     85
     86 1. Handles nullable foreign keys (https://github.com/wq/django-natural-keys/issues/18)
     87 2. Handles variadic natural-keys (e.g. Location model - [name, parent__name, parent__parent__name, ...].)
     88 """
     89 vals = []
---> 90 for lookups in [lookup.split("__") for lookup in self.natural_key_field_lookups]:
     91     val = self
     92     for lookup in lookups:

File ~/Library/Caches/pypoetry/virtualenvs/nautobot-b2ttWva6-py3.10/lib/python3.10/site-packages/django/utils/functional.py:61, in classproperty.__get__(self, instance, cls)
     60 def __get__(self, instance, cls=None):
---> 61     return self.fget(cls)

File ~/sandbox/src/nautobot/nautobot/core/models/__init__.py:156, in BaseModel.natural_key_field_lookups(cls)
    154 natural_key_field_lookups = []
    155 for field_name in natural_key_field_names:
--> 156     field = cls._meta.get_field(field_name)
    157     if getattr(field, "remote_field", None) is None:
    158         # Not a related field, so the field name is the field lookup
    159         natural_key_field_lookups.append(field_name)

File ~/Library/Caches/pypoetry/virtualenvs/nautobot-b2ttWva6-py3.10/lib/python3.10/site-packages/django/db/models/options.py:610, in Options.get_field(self, field_name)
    608     return self.fields_map[field_name]
    609 except KeyError:
--> 610     raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))

FieldDoesNotExist: IPAddress has no field named 'parent__namespace__name'

Additionally if you instead try to overload natural_key_field_lookups (vs. natural_key_field_names), the call to instance.natural_key() succeeds, but calls to IPAddress.objects.get_by_natural_key() is out of phase and results in a DoesNotExist error:

In [1]: instance = IPAddress.objects.first()

In [2]: instance.natural_key()
Out[2]: ['Global', '10.0.0.0']

In [3]: IPAddress.objects.get_by_natural_key(*instance.natural_key())
---------------------------------------------------------------------------
DoesNotExist                              Traceback (most recent call last)
File ~/sandbox/src/nautobot/nautobot/core/models/managers.py:42, in BaseManager.get_by_natural_key(self, *args)
     41 try:
---> 42     kwargs[field_name] = related_model.objects.get_by_natural_key(*related_values)
     43 except related_model.DoesNotExist as exc:

File ~/sandbox/src/nautobot/nautobot/core/models/managers.py:46, in BaseManager.get_by_natural_key(self, *args)
     44         raise self.model.DoesNotExist() from exc
---> 46 return self.get(**kwargs)

File ~/Library/Caches/pypoetry/virtualenvs/nautobot-b2ttWva6-py3.10/lib/python3.10/site-packages/django/db/models/manager.py:85, in BaseManager._get_queryset_methods.<locals>.create_method.<locals>.manager_method(self, *args, **kwargs)
     84 def manager_method(self, *args, **kwargs):
---> 85     return getattr(self.get_queryset(), name)(*args, **kwargs)

File ~/sandbox/src/nautobot/nautobot/ipam/querysets.py:261, in PrefixQuerySet.get(self, prefix, *args, **kwargs)
    260     kwargs["broadcast"] = last_ip
--> 261 return super().get(*args, **kwargs)

File ~/Library/Caches/pypoetry/virtualenvs/nautobot-b2ttWva6-py3.10/lib/python3.10/site-packages/django/db/models/query.py:435, in QuerySet.get(self, *args, **kwargs)
    434 if not num:
--> 435     raise self.model.DoesNotExist(
    436         "%s matching query does not exist." %
    437         self.model._meta.object_name
    438     )
    439 raise self.model.MultipleObjectsReturned(
    440     'get() returned more than one %s -- it returned %s!' % (
    441         self.model._meta.object_name,
    442         num if not limit or num < limit else 'more than %s' % (limit - 1),
    443     )
    444 )

DoesNotExist: Prefix matching query does not exist.

The above exception was the direct cause of the following exception:

DoesNotExist                              Traceback (most recent call last)
Cell In[3], line 1
----> 1 IPAddress.objects.get_by_natural_key(*instance.natural_key())

File ~/sandbox/src/nautobot/nautobot/core/models/managers.py:44, in BaseManager.get_by_natural_key(self, *args)
     42         kwargs[field_name] = related_model.objects.get_by_natural_key(*related_values)
     43     except related_model.DoesNotExist as exc:
---> 44         raise self.model.DoesNotExist() from exc
     46 return self.get(**kwargs)

DoesNotExist:
@jathanism jathanism added the type: bug Something isn't working as expected label Jun 12, 2023
@glennmatthews glennmatthews added this to the v2.0.0 milestone Jun 12, 2023
@glennmatthews glennmatthews self-assigned this Jun 12, 2023
@glennmatthews
Copy link
Contributor

Closed by #3903.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: bug Something isn't working as expected
Projects
No open projects
Archived in project
Development

Successfully merging a pull request may close this issue.

2 participants