mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-10 13:52:17 -06:00
Merge branch 'develop' into develop-2.7
This commit is contained in:
@@ -86,6 +86,10 @@ class CustomLinkForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = CustomLink
|
||||
exclude = []
|
||||
widgets = {
|
||||
'text': forms.Textarea,
|
||||
'url': forms.Textarea,
|
||||
}
|
||||
help_texts = {
|
||||
'text': 'Jinja2 template code for the link text. Reference the object as <code>{{ obj }}</code>. Links '
|
||||
'which render as empty text will not be displayed.',
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from extras.constants import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT
|
||||
from extras.constants import *
|
||||
from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
|
||||
from utilities.api import ValidatedModelSerializer
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Models which support custom fields
|
||||
CUSTOMFIELD_MODELS = [
|
||||
'circuits.circuit',
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.db.models import Q
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
|
||||
from .constants import *
|
||||
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
|
||||
|
||||
|
||||
|
||||
@@ -8,16 +8,12 @@ from taggit.forms import TagField
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.constants import COLOR_CHOICES
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
|
||||
CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField, StaticSelect2,
|
||||
BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from .constants import (
|
||||
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
|
||||
OBJECTCHANGE_ACTION_CHOICES,
|
||||
)
|
||||
from .constants import *
|
||||
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
|
||||
|
||||
|
||||
@@ -431,7 +427,7 @@ class ScriptForm(BootstrapMixin, forms.Form):
|
||||
help_text="Commit changes to the database (uncheck for a dry-run)"
|
||||
)
|
||||
|
||||
def __init__(self, vars, *args, **kwargs):
|
||||
def __init__(self, vars, *args, commit_default=True, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -439,6 +435,10 @@ class ScriptForm(BootstrapMixin, forms.Form):
|
||||
for name, var in vars.items():
|
||||
self.fields[name] = var.as_field()
|
||||
|
||||
# Toggle default commit behavior based on Meta option
|
||||
if not commit_default:
|
||||
self.fields['_commit'].initial = False
|
||||
|
||||
# Move _commit to the end of the form
|
||||
self.fields.move_to_end('_commit', True)
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ from django.utils import timezone
|
||||
from django.utils.functional import curry
|
||||
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||
|
||||
from .constants import (
|
||||
OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE,
|
||||
)
|
||||
from .constants import *
|
||||
from .models import ObjectChange
|
||||
from .signals import purge_changelog
|
||||
from .webhooks import enqueue_webhooks
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from collections import OrderedDict
|
||||
from datetime import date
|
||||
|
||||
import graphviz
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -654,7 +655,10 @@ class ConfigContext(models.Model):
|
||||
|
||||
|
||||
class ConfigContextModel(models.Model):
|
||||
|
||||
"""
|
||||
A model which includes local configuration context data. This local data will override any inherited data from
|
||||
ConfigContexts.
|
||||
"""
|
||||
local_context_data = JSONField(
|
||||
blank=True,
|
||||
null=True,
|
||||
@@ -679,6 +683,16 @@ class ConfigContextModel(models.Model):
|
||||
|
||||
return data
|
||||
|
||||
def clean(self):
|
||||
|
||||
super().clean()
|
||||
|
||||
# Verify that JSON data is provided as an object
|
||||
if self.local_context_data and type(self.local_context_data) is not dict:
|
||||
raise ValidationError(
|
||||
{'local_context_data': 'JSON data must be in object form. Example: {"foo": 123}'}
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Custom scripts
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections import OrderedDict
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_LEVEL_CODES, LOG_SUCCESS, LOG_WARNING
|
||||
from .constants import *
|
||||
from .models import ReportResult
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
from collections import OrderedDict
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import pkgutil
|
||||
import time
|
||||
import traceback
|
||||
import yaml
|
||||
from collections import OrderedDict
|
||||
|
||||
import yaml
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import transaction
|
||||
from mptt.forms import TreeNodeChoiceField
|
||||
from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
|
||||
from mptt.models import MPTTModel
|
||||
|
||||
from ipam.formfields import IPFormField
|
||||
@@ -21,13 +21,13 @@ from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARN
|
||||
from .forms import ScriptForm
|
||||
from .signals import purge_changelog
|
||||
|
||||
|
||||
__all__ = [
|
||||
'BaseScript',
|
||||
'BooleanVar',
|
||||
'FileVar',
|
||||
'IntegerVar',
|
||||
'IPNetworkVar',
|
||||
'MultiObjectVar',
|
||||
'ObjectVar',
|
||||
'Script',
|
||||
'StringVar',
|
||||
@@ -150,6 +150,23 @@ class ObjectVar(ScriptVariable):
|
||||
self.form_field = TreeNodeChoiceField
|
||||
|
||||
|
||||
class MultiObjectVar(ScriptVariable):
|
||||
"""
|
||||
Like ObjectVar, but can represent one or more objects.
|
||||
"""
|
||||
form_field = forms.ModelMultipleChoiceField
|
||||
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Queryset for field choices
|
||||
self.field_attrs['queryset'] = queryset
|
||||
|
||||
# Update form field for MPTT (nested) objects
|
||||
if issubclass(queryset.model, MPTTModel):
|
||||
self.form_field = TreeNodeMultipleChoiceField
|
||||
|
||||
|
||||
class FileVar(ScriptVariable):
|
||||
"""
|
||||
An uploaded file.
|
||||
@@ -226,7 +243,7 @@ class BaseScript:
|
||||
Return a Django form suitable for populating the context data required to run this Script.
|
||||
"""
|
||||
vars = self._get_vars()
|
||||
form = ScriptForm(vars, data, files)
|
||||
form = ScriptForm(vars, data, files, commit_default=getattr(self.Meta, 'commit_default', True))
|
||||
|
||||
return form
|
||||
|
||||
|
||||
@@ -120,6 +120,29 @@ class ScriptVariablesTest(TestCase):
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['var1'].pk, data['var1'])
|
||||
|
||||
def test_multiobjectvar(self):
|
||||
|
||||
class TestScript(Script):
|
||||
|
||||
var1 = MultiObjectVar(
|
||||
queryset=DeviceRole.objects.all()
|
||||
)
|
||||
|
||||
# Populate some objects
|
||||
for i in range(1, 6):
|
||||
DeviceRole(
|
||||
name='Device Role {}'.format(i),
|
||||
slug='device-role-{}'.format(i)
|
||||
).save()
|
||||
|
||||
# Validate valid data
|
||||
data = {'var1': [role.pk for role in DeviceRole.objects.all()[:3]]}
|
||||
form = TestScript().as_form(data, None)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['var1'][0].pk, data['var1'][0])
|
||||
self.assertEqual(form.cleaned_data['var1'][1].pk, data['var1'][1])
|
||||
self.assertEqual(form.cleaned_data['var1'][2].pk, data['var1'][2])
|
||||
|
||||
def test_filevar(self):
|
||||
|
||||
class TestScript(Script):
|
||||
|
||||
@@ -3,10 +3,9 @@ import datetime
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from extras.constants import OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
|
||||
from extras.models import Webhook
|
||||
from utilities.api import get_serializer_for_model
|
||||
from .constants import WEBHOOK_MODELS
|
||||
from .constants import *
|
||||
|
||||
|
||||
def enqueue_webhooks(instance, user, request_id, action):
|
||||
|
||||
@@ -6,7 +6,7 @@ import requests
|
||||
from django_rq import job
|
||||
from rest_framework.utils.encoders import JSONEncoder
|
||||
|
||||
from extras.constants import WEBHOOK_CT_JSON, WEBHOOK_CT_X_WWW_FORM_ENCODED, OBJECTCHANGE_ACTION_CHOICES
|
||||
from .constants import *
|
||||
|
||||
|
||||
@job('default')
|
||||
|
||||
Reference in New Issue
Block a user