Skip to content

Commit

Permalink
Initial work on #13427
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Oct 31, 2023
1 parent 77208bf commit af24894
Show file tree
Hide file tree
Showing 17 changed files with 92 additions and 18 deletions.
4 changes: 4 additions & 0 deletions docs/development/application-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ A dictionary of particular features (e.g. custom fields) mapped to the NetBox mo

Supported model features are listed in the [features matrix](./models.md#features-matrix).

### `models`

All NetBox models which have been registered as "public," meaning that they are intended for reference by other objects. Models for which `_netbox_private` is set to False are excluded from this list. (As with individual features under `model_features`, models are organized by app label.)

### `plugins`

This store maintains all registered items for plugins, such as navigation menus, template extensions, etc.
Expand Down
2 changes: 2 additions & 0 deletions netbox/account/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class UserToken(Token):
"""
Proxy model for users to manage their own API tokens.
"""
_netbox_private = True

class Meta:
proxy = True
verbose_name = 'token'
Expand Down
29 changes: 29 additions & 0 deletions netbox/core/migrations/0008_contenttype_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.6 on 2023-10-31 19:38

import core.models.contenttypes
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('core', '0007_job_add_error_field'),
]

operations = [
migrations.CreateModel(
name='ContentType',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('contenttypes.contenttype',),
managers=[
('objects', core.models.contenttypes.ContentTypeManager()),
],
),
]
1 change: 1 addition & 0 deletions netbox/core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .contenttypes import *
from .data import *
from .files import *
from .jobs import *
32 changes: 32 additions & 0 deletions netbox/core/models/contenttypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.contrib.contenttypes.models import ContentType as ContentType_, ContentTypeManager as ContentTypeManager_
from django.db.models import Q

from netbox.registry import registry

__all__ = (
'ContentType',
'ContentTypeManager',
)


class ContentTypeManager(ContentTypeManager_):

def public(self):
"""
Filter the base queryset to return only ContentTypes corresponding to "public" models; those which are listed
in registry['models'] and intended for reference by other objects.
"""
q = Q()
for app_label, models in registry['models'].items():
q |= Q(app_label=app_label, model__in=models)
return self.get_queryset().filter(q)


class ContentType(ContentType_):
"""
Wrap Django's native ContentType model to use our custom manager.
"""
objects = ContentTypeManager()

class Meta:
proxy = True
2 changes: 2 additions & 0 deletions netbox/core/models/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ class AutoSyncRecord(models.Model):
fk_field='object_id'
)

_netbox_private = True

class Meta:
constraints = (
models.UniqueConstraint(
Expand Down
1 change: 1 addition & 0 deletions netbox/core/models/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ManagedFile(SyncedDataMixin, models.Model):
)

objects = RestrictedQuerySet.as_manager()
_netbox_private = True

class Meta:
ordering = ('file_root', 'file_path')
Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/models/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ class CablePath(models.Model):
)
_nodes = PathField()

_netbox_private = True

class Meta:
verbose_name = _('cable path')
verbose_name_plural = _('cable paths')
Expand Down
9 changes: 2 additions & 7 deletions netbox/extras/dashboard/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@
import requests
from django import forms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.db.models import Q
from django.template.loader import render_to_string
from django.urls import NoReverseMatch, resolve, reverse
from django.utils.translation import gettext as _

from core.models import ContentType
from extras.choices import BookmarkOrderingChoices
from extras.utils import FeatureQuery
from utilities.choices import ButtonColorChoices
from utilities.forms import BootstrapMixin
from utilities.permissions import get_permission_for_model
Expand All @@ -37,10 +35,7 @@
def get_content_type_labels():
return [
(content_type_identifier(ct), content_type_name(ct))
for ct in ContentType.objects.filter(
FeatureQuery('export_templates').get_query() | Q(app_label='extras', model='objectchange') |
Q(app_label='extras', model='configcontext')
).order_by('app_label', 'model')
for ct in ContentType.objects.public().order_by('app_label', 'model')
]


Expand Down
5 changes: 2 additions & 3 deletions netbox/extras/forms/bulk_import.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.forms import SimpleArrayField
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _

from core.models import ContentType
from extras.choices import *
from extras.models import *
from extras.utils import FeatureQuery
Expand Down Expand Up @@ -40,8 +40,7 @@ class CustomFieldImportForm(CSVModelForm):
)
object_type = CSVContentTypeField(
label=_('Object type'),
queryset=ContentType.objects.all(),
limit_choices_to=FeatureQuery('custom_fields'),
queryset=ContentType.objects.public(),
required=False,
help_text=_("Object type (for object or multi-object fields)")
)
Expand Down
5 changes: 2 additions & 3 deletions netbox/extras/forms/filtersets.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _

from core.models import DataFile, DataSource
from core.models import ContentType, DataFile, DataSource
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.choices import *
from extras.models import *
Expand Down Expand Up @@ -196,7 +195,7 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
)
content_types = ContentTypeMultipleChoiceField(
label=_('Content types'),
queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
queryset=ContentType.objects.public(),
required=False
)
enabled = forms.NullBooleanField(
Expand Down
7 changes: 2 additions & 5 deletions netbox/extras/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

from django import forms
from django.conf import settings
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _

from core.forms.mixins import SyncedDataMixin
from core.models import ContentType
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.choices import *
from extras.models import *
Expand Down Expand Up @@ -49,9 +48,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
)
object_type = ContentTypeChoiceField(
label=_('Object type'),
queryset=ContentType.objects.all(),
# TODO: Come up with a canonical way to register suitable models
limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
queryset=ContentType.objects.public(),
required=False,
help_text=_("Type of the related object (for object/multi-object fields only)")
)
Expand Down
2 changes: 2 additions & 0 deletions netbox/extras/models/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class CachedValue(models.Model):
default=1000
)

_netbox_private = True

class Meta:
ordering = ('weight', 'object_type', 'object_id')
verbose_name = _('cached value')
Expand Down
2 changes: 2 additions & 0 deletions netbox/extras/models/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class TaggedItem(GenericTaggedItemBase):
on_delete=models.CASCADE
)

_netbox_private = True

class Meta:
indexes = [models.Index(fields=["content_type", "object_id"])]
verbose_name = _('tagged item')
Expand Down
4 changes: 4 additions & 0 deletions netbox/extras/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def register_features(model, features):
f"{feature} is not a valid model feature! Valid keys are: {registry['model_features'].keys()}"
)

# Register public models
if not getattr(model, '_netbox_private', False):
registry['models'][app_label].add(model_name)


def is_script(obj):
"""
Expand Down
1 change: 1 addition & 0 deletions netbox/netbox/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __delitem__(self, key):
'data_backends': dict(),
'denormalized_fields': collections.defaultdict(list),
'model_features': dict(),
'models': collections.defaultdict(set),
'plugins': dict(),
'search': dict(),
'views': collections.defaultdict(dict),
Expand Down
2 changes: 2 additions & 0 deletions netbox/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class UserConfig(models.Model):
default=dict
)

_netbox_private = True

class Meta:
ordering = ['user']
verbose_name = _('user preferences')
Expand Down

0 comments on commit af24894

Please sign in to comment.