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:
@@ -13,6 +13,7 @@ from extras.constants import *
|
||||
from extras.models import (
|
||||
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
|
||||
)
|
||||
from extras.utils import FeatureQuerySet
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from users.api.nested_serializers import NestedUserSerializer
|
||||
@@ -31,7 +32,7 @@ from .nested_serializers import *
|
||||
|
||||
class GraphSerializer(ValidatedModelSerializer):
|
||||
type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(GRAPH_MODELS),
|
||||
queryset=ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset()),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -67,7 +68,7 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
|
||||
|
||||
class ExportTemplateSerializer(ValidatedModelSerializer):
|
||||
content_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(EXPORTTEMPLATE_MODELS),
|
||||
queryset=ContentType.objects.filter(FeatureQuerySet('export_templates').get_queryset()),
|
||||
)
|
||||
template_language = ChoiceField(
|
||||
choices=TemplateLanguageChoices,
|
||||
@@ -91,7 +92,7 @@ class TagSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ['id', 'name', 'slug', 'color', 'comments', 'tagged_items']
|
||||
fields = ['id', 'name', 'slug', 'color', 'description', 'tagged_items']
|
||||
|
||||
|
||||
#
|
||||
|
@@ -14,9 +14,6 @@ class ExtrasRootView(routers.APIRootView):
|
||||
router = routers.DefaultRouter()
|
||||
router.APIRootView = ExtrasRootView
|
||||
|
||||
# Field choices
|
||||
router.register('_choices', views.ExtrasFieldChoicesViewSet, basename='field-choice')
|
||||
|
||||
# Custom field choices
|
||||
router.register('_custom_field_choices', views.CustomFieldChoicesViewSet, basename='custom-field-choice')
|
||||
|
||||
|
@@ -15,22 +15,10 @@ from extras.models import (
|
||||
)
|
||||
from extras.reports import get_report, get_reports
|
||||
from extras.scripts import get_script, get_scripts, run_script
|
||||
from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
||||
from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
||||
from . import serializers
|
||||
|
||||
|
||||
#
|
||||
# Field choices
|
||||
#
|
||||
|
||||
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(serializers.ExportTemplateSerializer, ['template_language']),
|
||||
(serializers.GraphSerializer, ['type', 'template_language']),
|
||||
(serializers.ObjectChangeSerializer, ['action']),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Custom field choices
|
||||
#
|
||||
|
@@ -1,129 +1,3 @@
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
# Models which support custom fields
|
||||
CUSTOMFIELD_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'device',
|
||||
'devicetype',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'site',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
# Custom links
|
||||
CUSTOMLINK_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'cable',
|
||||
'device',
|
||||
'devicetype',
|
||||
'powerpanel',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'site',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
# Models which can have Graphs associated with them
|
||||
GRAPH_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'device',
|
||||
'interface',
|
||||
'site',
|
||||
])
|
||||
)
|
||||
|
||||
# Models which support export templates
|
||||
EXPORTTEMPLATE_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'cable',
|
||||
'consoleport',
|
||||
'device',
|
||||
'devicetype',
|
||||
'interface',
|
||||
'inventoryitem',
|
||||
'manufacturer',
|
||||
'powerpanel',
|
||||
'powerport',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'rackgroup',
|
||||
'region',
|
||||
'site',
|
||||
'virtualchassis',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
# Report logging levels
|
||||
LOG_DEFAULT = 0
|
||||
LOG_SUCCESS = 10
|
||||
@@ -138,51 +12,14 @@ LOG_LEVEL_CODES = {
|
||||
LOG_FAILURE: 'failure',
|
||||
}
|
||||
|
||||
# Webhook content types
|
||||
HTTP_CONTENT_TYPE_JSON = 'application/json'
|
||||
|
||||
# Models which support registered webhooks
|
||||
WEBHOOK_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'cable',
|
||||
'consoleport',
|
||||
'consoleserverport',
|
||||
'device',
|
||||
'devicebay',
|
||||
'devicetype',
|
||||
'frontport',
|
||||
'interface',
|
||||
'inventoryitem',
|
||||
'manufacturer',
|
||||
'poweroutlet',
|
||||
'powerpanel',
|
||||
'powerport',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'rearport',
|
||||
'region',
|
||||
'site',
|
||||
'virtualchassis',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
# Registerable extras features
|
||||
EXTRAS_FEATURES = [
|
||||
'custom_fields',
|
||||
'custom_links',
|
||||
'graphs',
|
||||
'export_templates',
|
||||
'webhooks'
|
||||
]
|
||||
|
@@ -144,12 +144,11 @@ class CustomFieldFilterForm(forms.Form):
|
||||
|
||||
class TagForm(BootstrapMixin, forms.ModelForm):
|
||||
slug = SlugField()
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'comments'
|
||||
'name', 'slug', 'color', 'description'
|
||||
]
|
||||
|
||||
|
||||
@@ -181,9 +180,13 @@ class TagBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
required=False,
|
||||
widget=ColorSelect()
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
nullable_fields = ['description']
|
||||
|
||||
|
||||
#
|
||||
|
23
netbox/extras/migrations/0039_standardize_description.py
Normal file
23
netbox/extras/migrations/0039_standardize_description.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.0.3 on 2020-03-13 20:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0038_webhook_template_support'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='configcontext',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
]
|
@@ -0,0 +1,40 @@
|
||||
# Generated by Django 2.2.11 on 2020-03-14 06:50
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import extras.utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0038_webhook_template_support'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(limit_choices_to=extras.utils.FeatureQuerySet('custom_fields'), related_name='custom_fields', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customlink',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to=extras.utils.FeatureQuerySet('custom_links'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to=extras.utils.FeatureQuerySet('export_templates'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='graph',
|
||||
name='type',
|
||||
field=models.ForeignKey(limit_choices_to=extras.utils.FeatureQuerySet('graphs'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhook',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(limit_choices_to=extras.utils.FeatureQuerySet('webhooks'), related_name='webhooks', to='contenttypes.ContentType'),
|
||||
),
|
||||
]
|
23
netbox/extras/migrations/0040_tag_description.py
Normal file
23
netbox/extras/migrations/0040_tag_description.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.0.3 on 2020-03-13 20:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0039_standardize_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='tag',
|
||||
name='comments',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='tag',
|
||||
old_name='comments',
|
||||
new_name='description',
|
||||
),
|
||||
]
|
@@ -22,6 +22,7 @@ from utilities.utils import deepmerge, render_jinja2
|
||||
from .choices import *
|
||||
from .constants import *
|
||||
from .querysets import ConfigContextQuerySet
|
||||
from .utils import FeatureQuerySet
|
||||
|
||||
|
||||
__all__ = (
|
||||
@@ -58,7 +59,7 @@ class Webhook(models.Model):
|
||||
to=ContentType,
|
||||
related_name='webhooks',
|
||||
verbose_name='Object types',
|
||||
limit_choices_to=WEBHOOK_MODELS,
|
||||
limit_choices_to=FeatureQuerySet('webhooks'),
|
||||
help_text="The object(s) to which this Webhook applies."
|
||||
)
|
||||
name = models.CharField(
|
||||
@@ -223,7 +224,7 @@ class CustomField(models.Model):
|
||||
to=ContentType,
|
||||
related_name='custom_fields',
|
||||
verbose_name='Object(s)',
|
||||
limit_choices_to=CUSTOMFIELD_MODELS,
|
||||
limit_choices_to=FeatureQuerySet('custom_fields'),
|
||||
help_text='The object(s) to which this field applies.'
|
||||
)
|
||||
type = models.CharField(
|
||||
@@ -242,7 +243,7 @@ class CustomField(models.Model):
|
||||
'the field\'s name will be used)'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
required = models.BooleanField(
|
||||
@@ -470,7 +471,7 @@ class CustomLink(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to=CUSTOMLINK_MODELS
|
||||
limit_choices_to=FeatureQuerySet('custom_links')
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
@@ -518,7 +519,7 @@ class Graph(models.Model):
|
||||
type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to=GRAPH_MODELS
|
||||
limit_choices_to=FeatureQuerySet('graphs')
|
||||
)
|
||||
weight = models.PositiveSmallIntegerField(
|
||||
default=1000
|
||||
@@ -550,7 +551,6 @@ class Graph(models.Model):
|
||||
def embed_url(self, obj):
|
||||
context = {'obj': obj}
|
||||
|
||||
# TODO: Remove in v2.8
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.source)
|
||||
return template.render(Context(context))
|
||||
@@ -564,7 +564,6 @@ class Graph(models.Model):
|
||||
|
||||
context = {'obj': obj}
|
||||
|
||||
# TODO: Remove in v2.8
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.link)
|
||||
return template.render(Context(context))
|
||||
@@ -581,7 +580,7 @@ class ExportTemplate(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to=EXPORTTEMPLATE_MODELS
|
||||
limit_choices_to=FeatureQuerySet('export_templates')
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
@@ -766,7 +765,7 @@ class ConfigContext(models.Model):
|
||||
default=1000
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
@@ -1053,9 +1052,9 @@ class Tag(TagBase, ChangeLoggedModel):
|
||||
color = ColorField(
|
||||
default='9e9e9e'
|
||||
)
|
||||
comments = models.TextField(
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
default=''
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
|
@@ -77,7 +77,7 @@ class TagTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Tag
|
||||
fields = ('pk', 'name', 'items', 'slug', 'color', 'actions')
|
||||
fields = ('pk', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
||||
|
||||
|
||||
class TaggedItemTable(BaseTable):
|
||||
|
@@ -7,12 +7,11 @@ from rest_framework import status
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site
|
||||
from extras.api.views import ScriptViewSet
|
||||
from extras.choices import *
|
||||
from extras.constants import GRAPH_MODELS
|
||||
from extras.models import ConfigContext, Graph, ExportTemplate, Tag
|
||||
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
||||
from extras.utils import FeatureQuerySet
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.testing import APITestCase, choices_to_dict
|
||||
from utilities.testing import APITestCase
|
||||
|
||||
|
||||
class AppTest(APITestCase):
|
||||
@@ -24,27 +23,6 @@ class AppTest(APITestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_choices(self):
|
||||
|
||||
url = reverse('extras-api:field-choice-list')
|
||||
response = self.client.get(url, **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# ExportTemplate
|
||||
self.assertEqual(choices_to_dict(response.data.get('export-template:template_language')), TemplateLanguageChoices.as_dict())
|
||||
|
||||
# Graph
|
||||
content_types = ContentType.objects.filter(GRAPH_MODELS)
|
||||
graph_type_choices = {
|
||||
"{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types
|
||||
}
|
||||
self.assertEqual(choices_to_dict(response.data.get('graph:type')), graph_type_choices)
|
||||
self.assertEqual(choices_to_dict(response.data.get('graph:template_language')), TemplateLanguageChoices.as_dict())
|
||||
|
||||
# ObjectChange
|
||||
self.assertEqual(choices_to_dict(response.data.get('object-change:action')), ObjectChangeActionChoices.as_dict())
|
||||
|
||||
|
||||
class GraphTest(APITestCase):
|
||||
|
||||
|
@@ -3,8 +3,8 @@ from django.test import TestCase
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from extras.choices import *
|
||||
from extras.constants import GRAPH_MODELS
|
||||
from extras.filters import *
|
||||
from extras.utils import FeatureQuerySet
|
||||
from extras.models import ConfigContext, ExportTemplate, Graph
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
@@ -18,7 +18,7 @@ class GraphTestCase(TestCase):
|
||||
def setUpTestData(cls):
|
||||
|
||||
# Get the first three available types
|
||||
content_types = ContentType.objects.filter(GRAPH_MODELS)[:3]
|
||||
content_types = ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset())[:3]
|
||||
|
||||
graphs = (
|
||||
Graph(name='Graph 1', type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'),
|
||||
@@ -32,11 +32,10 @@ class GraphTestCase(TestCase):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_type(self):
|
||||
content_type = ContentType.objects.filter(GRAPH_MODELS).first()
|
||||
content_type = ContentType.objects.filter(FeatureQuerySet('graphs').get_queryset()).first()
|
||||
params = {'type': content_type.pk}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
# TODO: Remove in v2.8
|
||||
def test_template_language(self):
|
||||
params = {'template_language': TemplateLanguageChoices.LANGUAGE_JINJA2}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import collections
|
||||
|
||||
from django.db.models import Q
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from taggit.managers import _TaggableManager
|
||||
from utilities.querysets import DummyQuerySet
|
||||
|
||||
from extras.constants import EXTRAS_FEATURES
|
||||
|
||||
|
||||
def is_taggable(obj):
|
||||
"""
|
||||
@@ -13,3 +19,65 @@ def is_taggable(obj):
|
||||
if isinstance(obj.tags, DummyQuerySet):
|
||||
return True
|
||||
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 FeatureQuerySet:
|
||||
"""
|
||||
Helper class that delays evaluation of the registry contents for the functionaility store
|
||||
until it has been populated.
|
||||
"""
|
||||
|
||||
def __init__(self, feature):
|
||||
self.feature = feature
|
||||
|
||||
def __call__(self):
|
||||
return self.get_queryset()
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
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():
|
||||
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):
|
||||
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)
|
||||
else:
|
||||
raise ValueError('{} is not a valid extras feature!'.format(feature))
|
||||
return model_class
|
||||
return wrapper
|
||||
|
@@ -8,6 +8,7 @@ from extras.models import Webhook
|
||||
from utilities.api import get_serializer_for_model
|
||||
from .choices import *
|
||||
from .constants import *
|
||||
from .utils import FeatureQuerySet
|
||||
|
||||
|
||||
def generate_signature(request_body, secret):
|
||||
@@ -29,7 +30,7 @@ def enqueue_webhooks(instance, user, request_id, action):
|
||||
"""
|
||||
obj_type = ContentType.objects.get_for_model(instance.__class__)
|
||||
|
||||
webhook_models = ContentType.objects.filter(WEBHOOK_MODELS)
|
||||
webhook_models = ContentType.objects.filter(FeatureQuerySet('webhooks').get_queryset())
|
||||
if obj_type not in webhook_models:
|
||||
return
|
||||
|
||||
|
Reference in New Issue
Block a user