1
0
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:
John Anderson
2020-03-18 14:48:11 -04:00
22 changed files with 598 additions and 646 deletions

View File

@@ -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",
)
)

View 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
View 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()

View 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'])

View File

@@ -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