mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop-2.8' into 3351-plugins
This commit is contained in:
@@ -201,60 +201,36 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||
)
|
||||
sites = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
roles = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/device-roles/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
platforms = DynamicModelMultipleChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/platforms/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
cluster_groups = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/virtualization/cluster-groups/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
clusters = DynamicModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/virtualization/clusters/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
tenant_groups = DynamicModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenant-groups/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
tenants = DynamicModelMultipleChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenants/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/extras/tags/"
|
||||
)
|
||||
required=False
|
||||
)
|
||||
data = JSONField(
|
||||
label=''
|
||||
@@ -302,7 +278,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
@@ -311,7 +286,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
@@ -320,7 +294,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/device-roles/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
@@ -329,7 +302,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/platforms/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
@@ -338,24 +310,19 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/virtualization/cluster-groups/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
cluster_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
label='Cluster',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/virtualization/clusters/",
|
||||
)
|
||||
label='Cluster'
|
||||
)
|
||||
tenant_group = DynamicModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenant-groups/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
@@ -364,7 +331,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenants/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
@@ -373,7 +339,6 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/extras/tags/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
|
16
netbox/extras/management/commands/rqworker.py
Normal file
16
netbox/extras/management/commands/rqworker.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.conf import settings
|
||||
from django_rq.management.commands.rqworker import Command as _Command
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
"""
|
||||
Subclass django_rq's built-in rqworker to listen on all configured queues if none are specified (instead
|
||||
of only the 'default' queue).
|
||||
"""
|
||||
def handle(self, *args, **options):
|
||||
|
||||
# If no queues have been specified on the command line, listen on all configured queues.
|
||||
if len(args) < 1:
|
||||
args = settings.RQ_QUEUES
|
||||
|
||||
super().handle(*args, **options)
|
21
netbox/extras/registry.py
Normal file
21
netbox/extras/registry.py
Normal file
@@ -0,0 +1,21 @@
|
||||
class Registry(dict):
|
||||
"""
|
||||
Central registry for registration of functionality. Once a store (key) is defined, it cannot be overwritten or
|
||||
deleted (although its value may be manipulated).
|
||||
"""
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return super().__getitem__(key)
|
||||
except KeyError:
|
||||
raise KeyError("Invalid store: {}".format(key))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key in self:
|
||||
raise KeyError("Store already set: {}".format(key))
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
raise TypeError("Cannot delete stores from registry")
|
||||
|
||||
|
||||
registry = Registry()
|
33
netbox/extras/tests/test_registry.py
Normal file
33
netbox/extras/tests/test_registry.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from extras.registry import Registry
|
||||
|
||||
|
||||
class RegistryTest(TestCase):
|
||||
|
||||
def test_add_store(self):
|
||||
reg = Registry()
|
||||
reg['foo'] = 123
|
||||
|
||||
self.assertEqual(reg['foo'], 123)
|
||||
|
||||
def test_manipulate_store(self):
|
||||
reg = Registry()
|
||||
reg['foo'] = [1, 2]
|
||||
reg['foo'].append(3)
|
||||
|
||||
self.assertListEqual(reg['foo'], [1, 2, 3])
|
||||
|
||||
def test_overwrite_store(self):
|
||||
reg = Registry()
|
||||
reg['foo'] = 123
|
||||
|
||||
with self.assertRaises(KeyError):
|
||||
reg['foo'] = 456
|
||||
|
||||
def test_delete_store(self):
|
||||
reg = Registry()
|
||||
reg['foo'] = 123
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
del(reg['foo'])
|
@@ -6,6 +6,7 @@ from taggit.managers import _TaggableManager
|
||||
from utilities.querysets import DummyQuerySet
|
||||
|
||||
from extras.constants import EXTRAS_FEATURES
|
||||
from extras.registry import registry
|
||||
|
||||
|
||||
def is_taggable(obj):
|
||||
@@ -21,33 +22,12 @@ def is_taggable(obj):
|
||||
return False
|
||||
|
||||
|
||||
#
|
||||
# Dynamic feature registration
|
||||
#
|
||||
|
||||
class Registry:
|
||||
"""
|
||||
The registry is a place to hook into for data storage across components
|
||||
"""
|
||||
|
||||
def add_store(self, store_name, initial_value=None):
|
||||
"""
|
||||
Given the name of some new data parameter and an optional initial value, setup the registry store
|
||||
"""
|
||||
if not hasattr(Registry, store_name):
|
||||
setattr(Registry, store_name, initial_value)
|
||||
|
||||
|
||||
registry = Registry()
|
||||
|
||||
|
||||
@deconstructible
|
||||
class FeatureQuery:
|
||||
"""
|
||||
Helper class that delays evaluation of the registry contents for the functionaility store
|
||||
Helper class that delays evaluation of the registry contents for the functionality store
|
||||
until it has been populated.
|
||||
"""
|
||||
|
||||
def __init__(self, feature):
|
||||
self.feature = feature
|
||||
|
||||
@@ -59,24 +39,26 @@ class FeatureQuery:
|
||||
Given an extras feature, return a Q object for content type lookup
|
||||
"""
|
||||
query = Q()
|
||||
for app_label, models in registry.model_feature_store[self.feature].items():
|
||||
for app_label, models in registry['model_features'][self.feature].items():
|
||||
query |= Q(app_label=app_label, model__in=models)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
registry.add_store('model_feature_store', {f: collections.defaultdict(list) for f in EXTRAS_FEATURES})
|
||||
|
||||
|
||||
def extras_features(*features):
|
||||
"""
|
||||
Decorator used to register extras provided features to a model
|
||||
"""
|
||||
def wrapper(model_class):
|
||||
# Initialize the model_features store if not already defined
|
||||
if 'model_features' not in registry:
|
||||
registry['model_features'] = {
|
||||
f: collections.defaultdict(list) for f in EXTRAS_FEATURES
|
||||
}
|
||||
for feature in features:
|
||||
if feature in EXTRAS_FEATURES:
|
||||
app_label, model_name = model_class._meta.label_lower.split('.')
|
||||
registry.model_feature_store[feature][app_label].append(model_name)
|
||||
registry['model_features'][feature][app_label].append(model_name)
|
||||
else:
|
||||
raise ValueError('{} is not a valid extras feature!'.format(feature))
|
||||
return model_class
|
||||
|
Reference in New Issue
Block a user