Merge branch 'feature' into feature-apiselect-queryparams

# Conflicts:
#	netbox/project-static/dist/netbox.js
#	netbox/project-static/dist/netbox.js.map
This commit is contained in:
thatmattlove 2021-08-28 09:44:07 -07:00
commit ba0ab6be46
10 changed files with 159 additions and 111 deletions

View File

@ -14,6 +14,7 @@
* [#6982](https://github.com/netbox-community/netbox/issues/6982) - Fix styling of empty dropdown list under dark mode
* [#6996](https://github.com/netbox-community/netbox/issues/6996) - Global search bar should be full width on mobile
* [#7001](https://github.com/netbox-community/netbox/issues/7001) - Fix page focus on load
* [#7034](https://github.com/netbox-community/netbox/issues/7034) - Fix toggling of VLAN group scope selector fields
---

View File

@ -125,6 +125,10 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
('Custom Link', ('name', 'content_type', 'weight', 'group_name', 'button_class', 'new_window')),
('Templates', ('link_text', 'link_url')),
)
widgets = {
'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
'link_url': forms.Textarea(attrs={'class': 'font-monospace'}),
}
help_texts = {
'link_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.',
@ -217,6 +221,9 @@ class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
('Template', ('template_code',)),
('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
)
widgets = {
'template_code': forms.Textarea(attrs={'class': 'font-monospace'}),
}
class ExportTemplateCSVForm(CSVModelForm):
@ -316,6 +323,10 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
)),
('SSL', ('ssl_verification', 'ca_file_path')),
)
widgets = {
'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
'body_template': forms.Textarea(attrs={'class': 'font-monospace'}),
}
class WebhookCSVForm(CSVModelForm):

View File

@ -35,7 +35,6 @@ class CustomField(ChangeLoggedModel):
content_types = models.ManyToManyField(
to=ContentType,
related_name='custom_fields',
verbose_name='Object(s)',
limit_choices_to=FeatureQuery('custom_fields'),
help_text='The object(s) to which this field applies.'
)

View File

@ -91,7 +91,7 @@ class Webhook(ChangeLoggedModel):
blank=True,
help_text="User-supplied HTTP headers to be sent with the request in addition to the HTTP content type. "
"Headers should be defined in the format <code>Name: Value</code>. Jinja2 template processing is "
"support with the same context as the request body (below)."
"supported with the same context as the request body (below)."
)
body_template = models.TextField(
blank=True,
@ -249,7 +249,8 @@ class ExportTemplate(ChangeLoggedModel):
blank=True
)
template_code = models.TextField(
help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.'
help_text='Jinja2 template code. The list of objects being exported is passed as a context variable named '
'<code>queryset</code>.'
)
mime_type = models.CharField(
max_length=50,

View File

@ -2,7 +2,8 @@ import django_tables2 as tables
from django.conf import settings
from utilities.tables import (
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ContentTypeColumn, ToggleColumn,
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ContentTypeColumn, ContentTypesColumn,
ToggleColumn,
)
from .models import *
@ -37,14 +38,16 @@ class CustomFieldTable(BaseTable):
name = tables.Column(
linkify=True
)
content_types = ContentTypesColumn()
required = BooleanColumn()
class Meta(BaseTable.Meta):
model = CustomField
fields = (
'pk', 'name', 'label', 'type', 'required', 'weight', 'default', 'description', 'filter_logic', 'choices',
'pk', 'name', 'content_types', 'label', 'type', 'required', 'weight', 'default', 'description',
'filter_logic', 'choices',
)
default_columns = ('pk', 'name', 'label', 'type', 'required', 'description')
default_columns = ('pk', 'name', 'content_types', 'label', 'type', 'required', 'description')
#
@ -98,19 +101,30 @@ class WebhookTable(BaseTable):
name = tables.Column(
linkify=True
)
content_types = ContentTypesColumn()
enabled = BooleanColumn()
type_create = BooleanColumn()
type_update = BooleanColumn()
type_delete = BooleanColumn()
type_create = BooleanColumn(
verbose_name='Create'
)
type_update = BooleanColumn(
verbose_name='Update'
)
type_delete = BooleanColumn(
verbose_name='Delete'
)
ssl_validation = BooleanColumn(
verbose_name='SSL Validation'
)
class Meta(BaseTable.Meta):
model = Webhook
fields = (
'pk', 'name', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method', 'payload_url',
'secret', 'ssl_validation', 'ca_file_path',
'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
'payload_url', 'secret', 'ssl_validation', 'ca_file_path',
)
default_columns = (
'pk', 'name', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method', 'payload_url',
'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
'payload_url',
)

View File

@ -11,14 +11,14 @@
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Content Type</th>
<td>{{ object.content_type }}</td>
</tr>
<tr>
<th scope="row">Name</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">Content Type</th>
<td>{{ object.content_type }}</td>
</tr>
<tr>
<th scope="row">Group Name</th>
<td>{{ object.group_name|placeholder }}</td>

View File

@ -2,6 +2,8 @@
{% load helpers %}
{% load plugins %}
{% block title %}{{ object.name }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item"><a href="{% url 'extras:exporttemplate_list' %}?content_type={{ object.content_type.pk }}">{{ object.content_type }}</a></li>

View File

@ -40,9 +40,11 @@
{% endif %}
<form action="" method="post" enctype="multipart/form-data" class="form form-horizontal">
{% csrf_token %}
<div class="field-group mb-3">
<div class="field-group my-4">
{% if form.requires_input %}
<h4 class="text-center">Script Data</h4>
<div class="row mb-2">
<h5 class="offset-sm-3">Script Data</h5>
</div>
{% else %}
<div class="alert alert-info">
<i class="mdi mdi-information"></i>

View File

@ -4,105 +4,113 @@
{% load log_levels %}
{% load static %}
{% block title %}{{ script }} <span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>{% endblock %}
{% block head %}
<script src="{% static 'jobs.js' %}?v{{ settings.VERSION }}"
onerror="window.location='{% url 'media_failure' %}?filename=jobs.js'"></script>
{% endblock %}
{% block content %}
<div class="row noprint">
<div class="col col-md-12">
<nav class="breadcrumb-container" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script' module=script.module name=class_name %}">{{ script }}</a></li>
<li class="breadcrumb-item">{{ result.created|annotated_date }}</li>
</ol>
</nav>
</div>
</div>
<p class="text-muted">{{ script.Meta.description|render_markdown }}</p>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation">
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">Log</a>
</li>
<li class="nav-item" role="presentation">
<a href="#output" role="tab" data-bs-toggle="tab" class="nav-link">Output</a>
</li>
<li class="nav-item" role="presentation">
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
</li>
</ul>
<div class="tab-content my-3">
<p>
Run: <strong>{{ result.created|annotated_date }}</strong>
{% if result.completed %}
Duration: <strong>{{ result.duration }}</strong>
{% else %}
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
{% endif %}
</p>
<div role="tabpanel" class="tab-pane active" id="log">
{% if result.completed %}
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">
Script Log
</h5>
<div class="card-body">
<table class="table table-hover panel-body">
<tr>
<th>Line</th>
<th>Level</th>
<th>Message</th>
</tr>
{% for log in result.data.log %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{% log_level log.status %}</td>
<td class="rendered-markdown">{{ log.message|render_markdown }}</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center text-muted">
No log output
</td>
</tr>
{% endfor %}
</table>
</div>
{% if execution_time %}
<div class="card-footer text-end text-muted">
<small>Exec Time: {{ execution_time|floatformat:3 }}s</small>
</div>
{% endif %}
</div>
</div>
</div>
{% else %}
<div class="row">
<div class="col col-md-12">
<div class="well">Pending Results</div>
</div>
</div>
{% endif %}
</div>
<div role="tabpanel" class="tab-pane" id="output">
<pre>{{ result.data.output }}</pre>
</div>
<div role="tabpanel" class="tab-pane" id="source">
<p><code>{{ script.filename }}</code></p>
<pre>{{ script.source }}</pre>
</div>
</div>
{% block title %}{{ script }}{% endblock %}
{% block subtitle %}
{{ script.Meta.description|render_markdown }}
<span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
{% endblock %}
{% block header %}
<div class="row noprint">
<div class="col col-md-12">
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script' module=script.module name=class_name %}">{{ script }}</a></li>
<li class="breadcrumb-item">{{ result.created|annotated_date }}</li>
</ol>
</nav>
</div>
</div>
{{ block.super }}
{% endblock header %}
{% block content-wrapper %}
<ul class="nav nav-tabs px-3" role="tablist">
<li class="nav-item" role="presentation">
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">Log</a>
</li>
<li class="nav-item" role="presentation">
<a href="#output" role="tab" data-bs-toggle="tab" class="nav-link">Output</a>
</li>
<li class="nav-item" role="presentation">
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">Source</a>
</li>
</ul>
<div class="tab-content mb-3">
<p>
Run: <strong>{{ result.created|annotated_date }}</strong>
{% if result.completed %}
Duration: <strong>{{ result.duration }}</strong>
{% else %}
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
{% endif %}
</p>
<div role="tabpanel" class="tab-pane active" id="log">
{% if result.completed %}
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">
Script Log
</h5>
<div class="card-body">
<table class="table table-hover panel-body">
<tr>
<th>Line</th>
<th>Level</th>
<th>Message</th>
</tr>
{% for log in result.data.log %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{% log_level log.status %}</td>
<td class="rendered-markdown">{{ log.message|render_markdown }}</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center text-muted">
No log output
</td>
</tr>
{% endfor %}
</table>
</div>
{% if execution_time %}
<div class="card-footer text-end text-muted">
<small>Exec Time: {{ execution_time|floatformat:3 }}s</small>
</div>
{% endif %}
</div>
</div>
</div>
{% else %}
<div class="row">
<div class="col col-md-12">
<div class="well">Pending Results</div>
</div>
</div>
{% endif %}
</div>
<div role="tabpanel" class="tab-pane" id="output">
<pre>{{ result.data.output }}</pre>
</div>
<div role="tabpanel" class="tab-pane" id="source">
<p><code>{{ script.filename }}</code></p>
<pre>{{ script.source }}</pre>
</div>
</div>
{% endblock content-wrapper %}
{% block data %}
<span data-job-id="{{ result.pk }}"></span>
<span data-job-complete="{{ result.completed }}"></span>

View File

@ -12,6 +12,8 @@ from django_tables2.data import TableQuerysetData
from django_tables2.utils import Accessor
from extras.models import CustomField
from extras.utils import FeatureQuery
from .utils import content_type_name
from .paginator import EnhancedPaginator, get_paginate_count
@ -235,12 +237,20 @@ class ContentTypeColumn(tables.Column):
Display a ContentType instance.
"""
def render(self, value):
return value.name[0].upper() + value.name[1:]
return content_type_name(value)
def value(self, value):
return f"{value.app_label}.{value.model}"
class ContentTypesColumn(tables.ManyToManyColumn):
"""
Display a list of ContentType instances.
"""
def transform(self, obj):
return content_type_name(obj)
class ColorColumn(tables.Column):
"""
Display a color (#RRGGBB).