Skip to content

Closes #19924: Record model features on ObjectType #19939

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

Open
wants to merge 20 commits into
base: feature
Choose a base branch
from

Conversation

jeremystretch
Copy link
Member

@jeremystretch jeremystretch commented Jul 23, 2025

Closes: #19924

  • Convert existing ForeignKeys pointing to ObjectType to reference ContentType (and alter the migration history accordingly)
  • Rename netbox/models/contenttypes.py to object_types.py
  • Convert ObjectType from a proxy model to a concrete model, inheriting from Django's ContentType
  • Add a public BooleanField indicating whether the corresponding model is considered public
  • Add a features ArrayField storing the names of all features supported by the model
  • Introduce a separate manager for ObjectType, which provides some parity with ContentTypeManager but does not support caching
  • Introduce the has_feature() utility function for determining whether a model supports a specific feature
  • Automatically create/update ObjectTypes post-migration
  • Add tests for the new utility functions and ObjectTypeManager methods

@jeremystretch jeremystretch marked this pull request as ready for review July 24, 2025 15:23
Copy link
Collaborator

@arthanson arthanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests pass fine locally, however if I try to go to any web page I get the error shown below. I've disabled all plugins and switching to main everything works fine. Restored the database as well and that didn't change anything.

Traceback (most recent call last):
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/netbox/netbox/views/generic/base.py", line 77, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/netbox/utilities/views.py", line 125, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/netbox/utilities/views.py", line 39, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
  File "/Users/ahanson/dev/work/netbox/netbox/netbox/views/generic/bulk_views.py", line 154, in get
    self.queryset = self.filterset(request.GET, self.queryset, request=request).qs
                    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/netbox/netbox/filtersets.py", line 305, in __init__
    for custom_field in custom_fields:
                        ^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/models/query.py", line 384, in __iter__
    self._fetch_all()
    ~~~~~~~~~~~~~~~^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/models/query.py", line 1949, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/models/query.py", line 91, in __iter__
    results = compiler.execute_sql(
        chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
    )
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/models/sql/compiler.py", line 1623, in execute_sql
    cursor.execute(sql, params)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/debug_toolbar/panels/sql/tracking.py", line 235, in execute
    return self._record(super().execute, sql, params)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/debug_toolbar/panels/sql/tracking.py", line 160, in _record
    return method(sql, params)
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/backends/utils.py", line 122, in execute
    return super().execute(sql, params)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/backends/utils.py", line 79, in execute
    return self._execute_with_wrappers(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        sql, params, many=False, executor=self._execute
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/backends/utils.py", line 92, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/backends/utils.py", line 100, in _execute
    with self.db.wrap_database_errors:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/django/db/backends/utils.py", line 105, in _execute
    return self.cursor.execute(sql, params)
           ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/ahanson/dev/work/netbox/venv/lib/python3.13/site-packages/psycopg/cursor.py", line 97, in execute
    raise ex.with_traceback(None)
django.db.utils.ProgrammingError: column extras_customfield_object_types.contenttype_id does not exist
LINE 1: ...ustomfield_object_types"."customfield_id") WHERE ("extras_cu...

@bctiemann
Copy link
Contributor

Confirmed the above error on my local.

It's failing to find contenttype_id but the DB table has objecttype_id.

Looks like this query:

# Dynamically add a Filter for each CustomField applicable to the parent model
custom_fields = CustomField.objects.filter(
object_types=ContentType.objects.get_for_model(self._meta.model)
).exclude(
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
)

CustomField.object_types should be using ObjectTypes now, shouldn't it?

@bctiemann
Copy link
Contributor

@jeremystretch I don't think it should have Group and Permission in here, should it? (Custom Field object type picker)

Screenshot 2025-07-25 at 6 25 47 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants