1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Revert "refactor extras registry"

This reverts commit c189895f6c.
This commit is contained in:
John Anderson
2020-03-15 00:24:05 -04:00
parent c189895f6c
commit 6ea15cec6f
6 changed files with 40 additions and 44 deletions

View File

@ -21,7 +21,7 @@ from dcim.constants import *
from dcim.fields import ASNField from dcim.fields import ASNField
from dcim.elevations import RackElevationSVG from dcim.elevations import RackElevationSVG
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
from extras.utils import extras_features from extras.utils import extras_functionality
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from utilities.models import ChangeLoggedModel from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object, to_meters from utilities.utils import serialize_object, to_meters
@ -1221,7 +1221,7 @@ class Platform(ChangeLoggedModel):
) )
@extras_features(['webhooks', 'custom_fields', 'export_templates', 'custom_links', 'graphs']) @extras_functionality(['webhooks', 'custom_fields', 'export_templates', 'custom_links', 'graphs'])
class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
""" """
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,

View File

@ -14,7 +14,7 @@ from extras.constants import *
from extras.models import ( from extras.models import (
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag, ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
) )
from extras.utils import FeatureQuerySet from extras.utils import FunctionalityQueryset
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from users.api.nested_serializers import NestedUserSerializer from users.api.nested_serializers import NestedUserSerializer
@ -33,7 +33,7 @@ from .nested_serializers import *
class GraphSerializer(ValidatedModelSerializer): class GraphSerializer(ValidatedModelSerializer):
type = ContentTypeField( type = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset()), queryset=ContentType.objects.filter(FunctionalityQueryset('graphs').get_queryset()),
) )
class Meta: class Meta:
@ -69,7 +69,7 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
class ExportTemplateSerializer(ValidatedModelSerializer): class ExportTemplateSerializer(ValidatedModelSerializer):
content_type = ContentTypeField( content_type = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuerySet('export_templates').get_queryset()), queryset=ContentType.objects.filter(Q(FunctionalityQueryset('export_templates').get_queryset())),
) )
template_language = ChoiceField( template_language = ChoiceField(
choices=TemplateLanguageChoices, choices=TemplateLanguageChoices,

View File

@ -22,7 +22,7 @@ from utilities.utils import deepmerge, render_jinja2
from .choices import * from .choices import *
from .constants import * from .constants import *
from .querysets import ConfigContextQuerySet from .querysets import ConfigContextQuerySet
from .utils import FeatureQuerySet from .utils import FunctionalityQueryset
__all__ = ( __all__ = (
@ -59,7 +59,7 @@ class Webhook(models.Model):
to=ContentType, to=ContentType,
related_name='webhooks', related_name='webhooks',
verbose_name='Object types', verbose_name='Object types',
limit_choices_to=FeatureQuerySet('webhooks'), limit_choices_to=FunctionalityQueryset('webhooks'),
help_text="The object(s) to which this Webhook applies." help_text="The object(s) to which this Webhook applies."
) )
name = models.CharField( name = models.CharField(
@ -224,7 +224,7 @@ class CustomField(models.Model):
to=ContentType, to=ContentType,
related_name='custom_fields', related_name='custom_fields',
verbose_name='Object(s)', verbose_name='Object(s)',
limit_choices_to=FeatureQuerySet('custom_fields'), limit_choices_to=FunctionalityQueryset('custom_fields'),
help_text='The object(s) to which this field applies.' help_text='The object(s) to which this field applies.'
) )
type = models.CharField( type = models.CharField(
@ -471,7 +471,7 @@ class CustomLink(models.Model):
content_type = models.ForeignKey( content_type = models.ForeignKey(
to=ContentType, to=ContentType,
on_delete=models.CASCADE, on_delete=models.CASCADE,
limit_choices_to=FeatureQuerySet('custom_links') limit_choices_to=FunctionalityQueryset('custom_links')
) )
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
@ -519,7 +519,7 @@ class Graph(models.Model):
type = models.ForeignKey( type = models.ForeignKey(
to=ContentType, to=ContentType,
on_delete=models.CASCADE, on_delete=models.CASCADE,
limit_choices_to=FeatureQuerySet('graphs') limit_choices_to=FunctionalityQueryset('graphs')
) )
weight = models.PositiveSmallIntegerField( weight = models.PositiveSmallIntegerField(
default=1000 default=1000
@ -582,7 +582,7 @@ class ExportTemplate(models.Model):
content_type = models.ForeignKey( content_type = models.ForeignKey(
to=ContentType, to=ContentType,
on_delete=models.CASCADE, on_delete=models.CASCADE,
limit_choices_to=FeatureQuerySet('export_templates') limit_choices_to=FunctionalityQueryset('export_templates')
) )
name = models.CharField( name = models.CharField(
max_length=100 max_length=100

View File

@ -8,6 +8,7 @@ from rest_framework import status
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site
from extras.api.views import ScriptViewSet from extras.api.views import ScriptViewSet
from extras.choices import * from extras.choices import *
from extras.constants import GRAPH_MODELS
from extras.models import ConfigContext, Graph, ExportTemplate, Tag from extras.models import ConfigContext, Graph, ExportTemplate, Tag
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
@ -34,7 +35,7 @@ class AppTest(APITestCase):
self.assertEqual(choices_to_dict(response.data.get('export-template:template_language')), TemplateLanguageChoices.as_dict()) self.assertEqual(choices_to_dict(response.data.get('export-template:template_language')), TemplateLanguageChoices.as_dict())
# Graph # Graph
content_types = ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset()) content_types = ContentType.objects.filter(GRAPH_MODELS)
graph_type_choices = { graph_type_choices = {
"{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types "{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types
} }

View File

@ -3,6 +3,7 @@ from django.test import TestCase
from dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site
from extras.choices import * from extras.choices import *
from extras.constants import GRAPH_MODELS
from extras.filters import * from extras.filters import *
from extras.models import ConfigContext, ExportTemplate, Graph from extras.models import ConfigContext, ExportTemplate, Graph
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
@ -17,7 +18,7 @@ class GraphTestCase(TestCase):
def setUpTestData(cls): def setUpTestData(cls):
# Get the first three available types # Get the first three available types
content_types = ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset())[:3] content_types = ContentType.objects.filter(GRAPH_MODELS)[:3]
graphs = ( graphs = (
Graph(name='Graph 1', type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'), Graph(name='Graph 1', type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'),
@ -31,7 +32,7 @@ class GraphTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_type(self): def test_type(self):
content_type = ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset()).first() content_type = ContentType.objects.filter(GRAPH_MODELS).first()
params = {'type': content_type.pk} params = {'type': content_type.pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

View File

@ -22,61 +22,55 @@ def is_taggable(obj):
class Registry: class Registry:
""" """
The registry is a place to hook into for data storage across components Singleton object used to store important data
""" """
instance = None
def add_store(self, store_name, initial_value=None): def __new__(cls):
""" if cls.instance is not None:
Given the name of some new data parameter and an optional initial value, setup the registry store return cls.instance
""" else:
if not hasattr(Registry, store_name): cls.instance = super().__new__(cls)
setattr(Registry, store_name, initial_value) cls.model_functionality_store = {f: collections.defaultdict(list) for f in EXTRAS_FUNCTIONALITIES}
return cls.instance
registry = Registry()
# class FunctionalityQueryset:
# Dynamic feature registration
#
class FeatureQuerySet:
""" """
Helper class that delays evaluation of the registry contents for the functionaility store Helper class that delays evaluation of the registry contents for the functionaility store
until it has been populated. until it has been populated.
""" """
def __init__(self, feature): def __init__(self, functionality):
self.feature = feature self.functionality = functionality
def __call__(self): def __call__(self):
return self.get_queryset() return self.get_queryset()
def get_queryset(self): def get_queryset(self):
""" """
Given an extras feature, return a Q object for content type lookup Given an extras functionality, return a Q object for content type lookup
""" """
query = Q() query = Q()
#registry = Registry() registry = Registry()
for app_label, models in registry.model_feature_store[self.feature].items(): for app_label, models in registry.model_functionality_store[self.functionality].items():
query |= Q(app_label=app_label, model__in=models) query |= Q(app_label=app_label, model__in=models)
return query return query
registry.add_store('model_feature_store', {f: collections.defaultdict(list) for f in EXTRAS_FUNCTIONALITIES}) def extras_functionality(functionalities):
def extras_features(features):
""" """
Decorator used to register extras provided features to a model Decorator used to register extras provided functionalities to a model
""" """
def wrapper(model_class): def wrapper(model_class):
if isinstance(features, list) and features: if isinstance(functionalities, list) and functionalities:
#registry = Registry() registry = Registry()
model_class._extras_feature = [] model_class._extras_functionality = []
for feature in features: for functionality in functionalities:
if feature in EXTRAS_FUNCTIONALITIES: if functionality in EXTRAS_FUNCTIONALITIES:
model_class._extras_functionality.append(functionality)
app_label, model_name = model_class._meta.label_lower.split('.') app_label, model_name = model_class._meta.label_lower.split('.')
registry.model_feature_store[feature][app_label].append(model_name) registry.model_functionality_store[functionality][app_label].append(model_name)
return model_class return model_class
return wrapper return wrapper