mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'netbox-community:develop' into script-reload
This commit is contained in:
@@ -117,12 +117,18 @@ class VirtualChassisCreateForm(BootstrapMixin, CustomFieldModelForm):
|
||||
'name', 'domain', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
|
||||
]
|
||||
|
||||
def clean(self):
|
||||
if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
|
||||
raise forms.ValidationError({
|
||||
'initial_position': "A position must be specified for the first VC member."
|
||||
})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
instance = super().save(*args, **kwargs)
|
||||
|
||||
# Assign VC members
|
||||
if instance.pk:
|
||||
initial_position = self.cleaned_data.get('initial_position') or 1
|
||||
if instance.pk and self.cleaned_data['members']:
|
||||
initial_position = self.cleaned_data.get('initial_position', 1)
|
||||
for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
|
||||
member.virtual_chassis = instance
|
||||
member.vc_position = i
|
||||
|
||||
@@ -53,6 +53,14 @@ def get_cabletermination_row_class(record):
|
||||
return ''
|
||||
|
||||
|
||||
def get_interface_row_class(record):
|
||||
if not record.enabled:
|
||||
return 'danger'
|
||||
elif record.is_virtual:
|
||||
return 'primary'
|
||||
return get_cabletermination_row_class(record)
|
||||
|
||||
|
||||
def get_interface_state_attribute(record):
|
||||
"""
|
||||
Get interface enabled state as string to attach to <tr/> DOM element.
|
||||
@@ -501,8 +509,8 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
|
||||
|
||||
class DeviceInterfaceTable(InterfaceTable):
|
||||
name = tables.TemplateColumn(
|
||||
template_code='<i class="mdi mdi-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}drag-horizontal-variant'
|
||||
'{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}ethernet'
|
||||
template_code='<i class="mdi mdi-{% if record.mgmt_only %}wrench{% elif record.is_lag %}reorder-horizontal'
|
||||
'{% elif record.is_virtual %}circle{% elif record.is_wireless %}wifi{% else %}ethernet'
|
||||
'{% endif %}"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||
order_by=Accessor('_name'),
|
||||
attrs={'td': {'class': 'text-nowrap'}}
|
||||
@@ -534,7 +542,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
||||
'cable', 'connection', 'actions',
|
||||
)
|
||||
row_attrs = {
|
||||
'class': get_cabletermination_row_class,
|
||||
'class': get_interface_row_class,
|
||||
'data-name': lambda record: record.name,
|
||||
'data-enabled': get_interface_state_attribute,
|
||||
}
|
||||
@@ -653,7 +661,8 @@ class DeviceBayTable(DeviceComponentTable):
|
||||
}
|
||||
)
|
||||
status = tables.TemplateColumn(
|
||||
template_code=DEVICEBAY_STATUS
|
||||
template_code=DEVICEBAY_STATUS,
|
||||
order_by=Accessor('installed_device__status')
|
||||
)
|
||||
installed_device = tables.Column(
|
||||
linkify=True
|
||||
|
||||
@@ -70,7 +70,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||
class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
content_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('custom_links')
|
||||
limit_choices_to=FeatureQuery('export_templates')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -31,7 +31,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
||||
return self.get_queryset().filter(content_types=content_type)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
@extras_features('webhooks', 'export_templates')
|
||||
class CustomField(ChangeLoggedModel):
|
||||
content_types = models.ManyToManyField(
|
||||
to=ContentType,
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.db import models
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.formats import date_format, time_format
|
||||
from django.utils.formats import date_format
|
||||
from rest_framework.utils.encoders import JSONEncoder
|
||||
|
||||
from extras.choices import *
|
||||
@@ -36,7 +36,7 @@ __all__ = (
|
||||
# Webhooks
|
||||
#
|
||||
|
||||
@extras_features('webhooks')
|
||||
@extras_features('webhooks', 'export_templates')
|
||||
class Webhook(ChangeLoggedModel):
|
||||
"""
|
||||
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
|
||||
@@ -175,7 +175,7 @@ class Webhook(ChangeLoggedModel):
|
||||
# Custom links
|
||||
#
|
||||
|
||||
@extras_features('webhooks')
|
||||
@extras_features('webhooks', 'export_templates')
|
||||
class CustomLink(ChangeLoggedModel):
|
||||
"""
|
||||
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
||||
@@ -234,7 +234,7 @@ class CustomLink(ChangeLoggedModel):
|
||||
# Export templates
|
||||
#
|
||||
|
||||
@extras_features('webhooks')
|
||||
@extras_features('webhooks', 'export_templates')
|
||||
class ExportTemplate(ChangeLoggedModel):
|
||||
content_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
|
||||
@@ -14,7 +14,7 @@ from utilities.querysets import RestrictedQuerySet
|
||||
# Tags
|
||||
#
|
||||
|
||||
@extras_features('webhooks')
|
||||
@extras_features('webhooks', 'export_templates')
|
||||
class Tag(ChangeLoggedModel, TagBase):
|
||||
color = ColorField(
|
||||
default=ColorChoices.COLOR_GREY
|
||||
|
||||
@@ -93,7 +93,7 @@ class VLANTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
vid = tables.TemplateColumn(
|
||||
template_code=VLAN_LINK,
|
||||
verbose_name='ID'
|
||||
verbose_name='VID'
|
||||
)
|
||||
site = tables.Column(
|
||||
linkify=True
|
||||
|
||||
@@ -17,7 +17,7 @@ from django.core.validators import URLValidator
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '3.0.10-dev'
|
||||
VERSION = '3.0.11-dev'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<a href="{{ vc_member.get_absolute_url }}">{{ vc_member }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% badge vc_member.vc_position %}
|
||||
{% badge vc_member.vc_position show_empty=True %}
|
||||
</td>
|
||||
<td>
|
||||
{% if object.master == vc_member %}
|
||||
|
||||
@@ -19,7 +19,7 @@ class GroupType(DjangoObjectType):
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
return RestrictedQuerySet(model=Group)
|
||||
return RestrictedQuerySet(model=Group).restrict(info.context.user, 'view')
|
||||
|
||||
|
||||
class UserType(DjangoObjectType):
|
||||
@@ -34,4 +34,4 @@ class UserType(DjangoObjectType):
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
return RestrictedQuerySet(model=User)
|
||||
return RestrictedQuerySet(model=User).restrict(info.context.user, 'view')
|
||||
|
||||
@@ -304,7 +304,7 @@ class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):
|
||||
app_label, model = name.split('.')
|
||||
ct_filter |= Q(app_label=app_label, model=model)
|
||||
return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True))
|
||||
return super().prepare_value(value)
|
||||
return f'{value.app_label}.{value.model}'
|
||||
|
||||
|
||||
#
|
||||
|
||||
@@ -40,14 +40,19 @@ def render_markdown(value):
|
||||
"""
|
||||
Render text as Markdown
|
||||
"""
|
||||
schemes = '|'.join(settings.ALLOWED_URL_SCHEMES)
|
||||
|
||||
# Strip HTML tags
|
||||
value = strip_tags(value)
|
||||
|
||||
# Sanitize Markdown links
|
||||
schemes = '|'.join(settings.ALLOWED_URL_SCHEMES)
|
||||
pattern = fr'\[(.+)\]\((?!({schemes})).*:(.+)\)'
|
||||
pattern = fr'\[([^\]]+)\]\((?!({schemes})).*:(.+)\)'
|
||||
value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
|
||||
|
||||
# Sanitize Markdown reference links
|
||||
pattern = fr'\[(.+)\]:\w?(?!({schemes})).*:(.+)'
|
||||
value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)
|
||||
|
||||
# Render Markdown
|
||||
html = markdown(value, extensions=['fenced_code', 'tables'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user