mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-05 06:46:25 -06:00
Merge branch 'develop' into feature
This commit is contained in:
@@ -68,6 +68,7 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'ordering': ('file_root', 'file_path'),
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
@@ -79,6 +80,7 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'ordering': ('file_root', 'file_path'),
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import decimal
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, date
|
||||
|
||||
@@ -488,7 +489,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
|
||||
# JSON
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_JSON:
|
||||
field = JSONField(required=required, initial=initial)
|
||||
field = JSONField(required=required, initial=json.dumps(initial) if initial else '')
|
||||
|
||||
# Object
|
||||
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
||||
|
||||
@@ -97,8 +97,16 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
|
||||
"""
|
||||
objects = ScriptModuleManager()
|
||||
|
||||
event_rules = GenericRelation(
|
||||
to='extras.EventRule',
|
||||
content_type_field='action_object_type',
|
||||
object_id_field='action_object_id',
|
||||
for_concrete_model=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
ordering = ('file_root', 'file_path')
|
||||
verbose_name = _('script module')
|
||||
verbose_name_plural = _('script modules')
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator,
|
||||
from utilities.exceptions import AbortScript, AbortTransaction
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.widgets import DatePicker, DateTimePicker
|
||||
from .context_managers import event_tracking
|
||||
from .forms import ScriptForm
|
||||
from .utils import is_report
|
||||
@@ -33,6 +34,8 @@ __all__ = (
|
||||
'BaseScript',
|
||||
'BooleanVar',
|
||||
'ChoiceVar',
|
||||
'DateVar',
|
||||
'DateTimeVar',
|
||||
'FileVar',
|
||||
'IntegerVar',
|
||||
'IPAddressVar',
|
||||
@@ -174,6 +177,28 @@ class ChoiceVar(ScriptVariable):
|
||||
self.field_attrs['choices'] = add_blank_choice(choices)
|
||||
|
||||
|
||||
class DateVar(ScriptVariable):
|
||||
"""
|
||||
A date.
|
||||
"""
|
||||
form_field = forms.DateField
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_field.widget = DatePicker()
|
||||
|
||||
|
||||
class DateTimeVar(ScriptVariable):
|
||||
"""
|
||||
A date and a time.
|
||||
"""
|
||||
form_field = forms.DateTimeField
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_field.widget = DateTimePicker()
|
||||
|
||||
|
||||
class MultiChoiceVar(ScriptVariable):
|
||||
"""
|
||||
Like ChoiceVar, but allows for the selection of multiple choices.
|
||||
|
||||
@@ -419,15 +419,35 @@ class ConfigTemplateTable(NetBoxTable):
|
||||
tags = columns.TagColumn(
|
||||
url_name='extras:configtemplate_list'
|
||||
)
|
||||
role_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:devicerole_list',
|
||||
url_params={'config_template_id': 'pk'},
|
||||
verbose_name=_('Device Roles')
|
||||
)
|
||||
platform_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:platform_list',
|
||||
url_params={'config_template_id': 'pk'},
|
||||
verbose_name=_('Platforms')
|
||||
)
|
||||
device_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:device_list',
|
||||
url_params={'config_template_id': 'pk'},
|
||||
verbose_name=_('Devices')
|
||||
)
|
||||
vm_count = columns.LinkedCountColumn(
|
||||
viewname='virtualization:virtualmachine_list',
|
||||
url_params={'config_template_id': 'pk'},
|
||||
verbose_name=_('Virtual Machines')
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ConfigTemplate
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'description', 'data_source', 'data_file', 'data_synced', 'created', 'last_updated',
|
||||
'tags',
|
||||
'pk', 'id', 'name', 'description', 'data_source', 'data_file', 'data_synced', 'role_count',
|
||||
'platform_count', 'device_count', 'vm_count', 'created', 'last_updated', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'description', 'is_synced',
|
||||
'pk', 'name', 'description', 'is_synced', 'device_count', 'vm_count',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import tempfile
|
||||
from datetime import date, datetime, timezone
|
||||
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
@@ -322,3 +323,47 @@ class ScriptVariablesTest(TestCase):
|
||||
form = TestScript().as_form(data, None)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['var1'], IPNetwork(data['var1']))
|
||||
|
||||
def test_datevar(self):
|
||||
|
||||
class TestScript(Script):
|
||||
|
||||
var1 = DateVar()
|
||||
var2 = DateVar(required=False)
|
||||
|
||||
# Test date validation
|
||||
data = {'var1': 'not a date'}
|
||||
form = TestScript().as_form(data, None)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn('var1', form.errors)
|
||||
|
||||
# Validate valid data
|
||||
input_date = date(2024, 4, 1)
|
||||
data = {'var1': input_date}
|
||||
form = TestScript().as_form(data, None)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['var1'], input_date)
|
||||
# Validate required=False works for this Var type
|
||||
self.assertEqual(form.cleaned_data['var2'], None)
|
||||
|
||||
def test_datetimevar(self):
|
||||
|
||||
class TestScript(Script):
|
||||
|
||||
var1 = DateTimeVar()
|
||||
var2 = DateTimeVar(required=False)
|
||||
|
||||
# Test datetime validation
|
||||
data = {'var1': 'not a datetime'}
|
||||
form = TestScript().as_form(data, None)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn('var1', form.errors)
|
||||
|
||||
# Validate valid data
|
||||
input_datetime = datetime(2024, 4, 1, 8, 0, 0, 0, timezone.utc)
|
||||
data = {'var1': input_datetime}
|
||||
form = TestScript().as_form(data, None)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['var1'], input_datetime)
|
||||
# Validate required=False works for this Var type
|
||||
self.assertEqual(form.cleaned_data['var2'], None)
|
||||
|
||||
+11
-2
@@ -13,6 +13,7 @@ from core.choices import ManagedFileRootPathChoices
|
||||
from core.forms import ManagedFileForm
|
||||
from core.models import Job
|
||||
from core.tables import JobTable
|
||||
from dcim.models import Device, DeviceRole, Platform
|
||||
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
||||
from extras.dashboard.utils import get_widget_class
|
||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||
@@ -28,6 +29,7 @@ from utilities.request import copy_safe_request
|
||||
from utilities.rqworker import get_workers_for_queue
|
||||
from utilities.templatetags.builtins.filters import render_markdown
|
||||
from utilities.views import ContentTypePermissionRequiredMixin, get_viewname, register_model_view
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import filtersets, forms, tables
|
||||
from .models import *
|
||||
from .scripts import run_script
|
||||
@@ -627,7 +629,12 @@ class ObjectConfigContextView(generic.ObjectView):
|
||||
#
|
||||
|
||||
class ConfigTemplateListView(generic.ObjectListView):
|
||||
queryset = ConfigTemplate.objects.all()
|
||||
queryset = ConfigTemplate.objects.annotate(
|
||||
device_count=count_related(Device, 'config_template'),
|
||||
vm_count=count_related(VirtualMachine, 'config_template'),
|
||||
role_count=count_related(DeviceRole, 'config_template'),
|
||||
platform_count=count_related(Platform, 'config_template'),
|
||||
)
|
||||
filterset = filtersets.ConfigTemplateFilterSet
|
||||
filterset_form = forms.ConfigTemplateFilterForm
|
||||
table = tables.ConfigTemplateTable
|
||||
@@ -1035,7 +1042,9 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
|
||||
return 'extras.view_script'
|
||||
|
||||
def get(self, request):
|
||||
script_modules = ScriptModule.objects.restrict(request.user).prefetch_related('jobs')
|
||||
script_modules = ScriptModule.objects.restrict(request.user).prefetch_related(
|
||||
'data_source', 'data_file', 'jobs'
|
||||
)
|
||||
|
||||
return render(request, 'extras/script_list.html', {
|
||||
'model': ScriptModule,
|
||||
|
||||
Reference in New Issue
Block a user