mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* Initial work on #13427 * Clarify documentation * Reference public models registry when populating models for ConfigTemplate context
This commit is contained in:
@ -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).
|
Supported model features are listed in the [features matrix](./models.md#features-matrix).
|
||||||
|
|
||||||
|
### `models`
|
||||||
|
|
||||||
|
This key lists all models which have been registered in NetBox which are not designated for private use. (Setting `_netbox_private` to True on a model excludes it from this list.) As with individual features under `model_features`, models are organized by app label.
|
||||||
|
|
||||||
### `plugins`
|
### `plugins`
|
||||||
|
|
||||||
This store maintains all registered items for plugins, such as navigation menus, template extensions, etc.
|
This store maintains all registered items for plugins, such as navigation menus, template extensions, etc.
|
||||||
|
@ -7,6 +7,8 @@ class UserToken(Token):
|
|||||||
"""
|
"""
|
||||||
Proxy model for users to manage their own API tokens.
|
Proxy model for users to manage their own API tokens.
|
||||||
"""
|
"""
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
verbose_name = 'token'
|
verbose_name = 'token'
|
||||||
|
29
netbox/core/migrations/0008_contenttype_proxy.py
Normal file
29
netbox/core/migrations/0008_contenttype_proxy.py
Normal file
@ -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,3 +1,4 @@
|
|||||||
|
from .contenttypes import *
|
||||||
from .data import *
|
from .data import *
|
||||||
from .files import *
|
from .files import *
|
||||||
from .jobs import *
|
from .jobs import *
|
||||||
|
32
netbox/core/models/contenttypes.py
Normal file
32
netbox/core/models/contenttypes.py
Normal file
@ -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
|
@ -378,6 +378,8 @@ class AutoSyncRecord(models.Model):
|
|||||||
fk_field='object_id'
|
fk_field='object_id'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = (
|
constraints = (
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
|
@ -44,6 +44,7 @@ class ManagedFile(SyncedDataMixin, models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('file_root', 'file_path')
|
ordering = ('file_root', 'file_path')
|
||||||
|
@ -431,6 +431,8 @@ class CablePath(models.Model):
|
|||||||
)
|
)
|
||||||
_nodes = PathField()
|
_nodes = PathField()
|
||||||
|
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('cable path')
|
verbose_name = _('cable path')
|
||||||
verbose_name_plural = _('cable paths')
|
verbose_name_plural = _('cable paths')
|
||||||
|
@ -7,15 +7,13 @@ import feedparser
|
|||||||
import requests
|
import requests
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Q
|
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.urls import NoReverseMatch, resolve, reverse
|
from django.urls import NoReverseMatch, resolve, reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from core.models import ContentType
|
||||||
from extras.choices import BookmarkOrderingChoices
|
from extras.choices import BookmarkOrderingChoices
|
||||||
from extras.utils import FeatureQuery
|
|
||||||
from utilities.choices import ButtonColorChoices
|
from utilities.choices import ButtonColorChoices
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
@ -37,10 +35,7 @@ __all__ = (
|
|||||||
def get_content_type_labels():
|
def get_content_type_labels():
|
||||||
return [
|
return [
|
||||||
(content_type_identifier(ct), content_type_name(ct))
|
(content_type_identifier(ct), content_type_name(ct))
|
||||||
for ct in ContentType.objects.filter(
|
for ct in ContentType.objects.public().order_by('app_label', 'model')
|
||||||
FeatureQuery('export_templates').get_query() | Q(app_label='extras', model='objectchange') |
|
|
||||||
Q(app_label='extras', model='configcontext')
|
|
||||||
).order_by('app_label', 'model')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from core.models import ContentType
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
@ -40,8 +40,7 @@ class CustomFieldImportForm(CSVModelForm):
|
|||||||
)
|
)
|
||||||
object_type = CSVContentTypeField(
|
object_type = CSVContentTypeField(
|
||||||
label=_('Object type'),
|
label=_('Object type'),
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.public(),
|
||||||
limit_choices_to=FeatureQuery('custom_fields'),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Object type (for object or multi-object fields)")
|
help_text=_("Object type (for object or multi-object fields)")
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
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 dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
@ -196,7 +195,7 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
)
|
)
|
||||||
content_types = ContentTypeMultipleChoiceField(
|
content_types = ContentTypeMultipleChoiceField(
|
||||||
label=_('Content types'),
|
label=_('Content types'),
|
||||||
queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
|
queryset=ContentType.objects.public(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
enabled = forms.NullBooleanField(
|
enabled = forms.NullBooleanField(
|
||||||
|
@ -2,12 +2,11 @@ import json
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
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.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.forms.mixins import SyncedDataMixin
|
from core.forms.mixins import SyncedDataMixin
|
||||||
|
from core.models import ContentType
|
||||||
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
@ -49,9 +48,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
object_type = ContentTypeChoiceField(
|
object_type = ContentTypeChoiceField(
|
||||||
label=_('Object type'),
|
label=_('Object type'),
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.public(),
|
||||||
# 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']),
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Type of the related object (for object/multi-object fields only)")
|
help_text=_("Type of the related object (for object/multi-object fields only)")
|
||||||
)
|
)
|
||||||
|
@ -260,12 +260,14 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
|
|||||||
_context = dict()
|
_context = dict()
|
||||||
|
|
||||||
# Populate the default template context with NetBox model classes, namespaced by app
|
# Populate the default template context with NetBox model classes, namespaced by app
|
||||||
# TODO: Devise a canonical mechanism for identifying the models to include (see #13427)
|
for app, model_names in registry['models'].items():
|
||||||
for app, model_names in registry['model_features']['custom_fields'].items():
|
|
||||||
_context.setdefault(app, {})
|
_context.setdefault(app, {})
|
||||||
for model_name in model_names:
|
for model_name in model_names:
|
||||||
|
try:
|
||||||
model = apps.get_registered_model(app, model_name)
|
model = apps.get_registered_model(app, model_name)
|
||||||
_context[app][model.__name__] = model
|
_context[app][model.__name__] = model
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Add the provided context data, if any
|
# Add the provided context data, if any
|
||||||
if context is not None:
|
if context is not None:
|
||||||
|
@ -49,6 +49,8 @@ class CachedValue(models.Model):
|
|||||||
default=1000
|
default=1000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('weight', 'object_type', 'value', 'object_id')
|
ordering = ('weight', 'object_type', 'value', 'object_id')
|
||||||
verbose_name = _('cached value')
|
verbose_name = _('cached value')
|
||||||
|
@ -75,6 +75,8 @@ class TaggedItem(GenericTaggedItemBase):
|
|||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [models.Index(fields=["content_type", "object_id"])]
|
indexes = [models.Index(fields=["content_type", "object_id"])]
|
||||||
verbose_name = _('tagged item')
|
verbose_name = _('tagged item')
|
||||||
|
@ -67,6 +67,10 @@ def register_features(model, features):
|
|||||||
f"{feature} is not a valid model feature! Valid keys are: {registry['model_features'].keys()}"
|
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):
|
def is_script(obj):
|
||||||
"""
|
"""
|
||||||
|
@ -25,6 +25,7 @@ registry = Registry({
|
|||||||
'data_backends': dict(),
|
'data_backends': dict(),
|
||||||
'denormalized_fields': collections.defaultdict(list),
|
'denormalized_fields': collections.defaultdict(list),
|
||||||
'model_features': dict(),
|
'model_features': dict(),
|
||||||
|
'models': collections.defaultdict(set),
|
||||||
'plugins': dict(),
|
'plugins': dict(),
|
||||||
'search': dict(),
|
'search': dict(),
|
||||||
'views': collections.defaultdict(dict),
|
'views': collections.defaultdict(dict),
|
||||||
|
@ -99,6 +99,8 @@ class UserConfig(models.Model):
|
|||||||
default=dict
|
default=dict
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_netbox_private = True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['user']
|
ordering = ['user']
|
||||||
verbose_name = _('user preferences')
|
verbose_name = _('user preferences')
|
||||||
|
Reference in New Issue
Block a user