mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Add UI views for export templates
This commit is contained in:
@ -2,8 +2,8 @@ from django import forms
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from utilities.forms import ContentTypeChoiceField, ContentTypeMultipleChoiceField, LaxURLField
|
from utilities.forms import ContentTypeMultipleChoiceField, LaxURLField
|
||||||
from .models import CustomLink, ExportTemplate, JobResult, Webhook
|
from .models import JobResult, Webhook
|
||||||
from .utils import FeatureQuery
|
from .utils import FeatureQuery
|
||||||
|
|
||||||
|
|
||||||
@ -57,41 +57,6 @@ class WebhookAdmin(admin.ModelAdmin):
|
|||||||
return ', '.join([ct.name for ct in obj.content_types.all()])
|
return ', '.join([ct.name for ct in obj.content_types.all()])
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Export templates
|
|
||||||
#
|
|
||||||
|
|
||||||
class ExportTemplateForm(forms.ModelForm):
|
|
||||||
content_type = ContentTypeChoiceField(
|
|
||||||
queryset=ContentType.objects.all(),
|
|
||||||
limit_choices_to=FeatureQuery('custom_links')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ExportTemplate
|
|
||||||
exclude = []
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ExportTemplate)
|
|
||||||
class ExportTemplateAdmin(admin.ModelAdmin):
|
|
||||||
fieldsets = (
|
|
||||||
('Export Template', {
|
|
||||||
'fields': ('content_type', 'name', 'description', 'mime_type', 'file_extension', 'as_attachment')
|
|
||||||
}),
|
|
||||||
('Content', {
|
|
||||||
'fields': ('template_code',),
|
|
||||||
'classes': ('monospace',)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
list_display = [
|
|
||||||
'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
|
||||||
]
|
|
||||||
list_filter = [
|
|
||||||
'content_type',
|
|
||||||
]
|
|
||||||
form = ExportTemplateForm
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Reports
|
# Reports
|
||||||
#
|
#
|
||||||
|
@ -123,7 +123,7 @@ class CustomLinkCSVForm(CSVModelForm):
|
|||||||
content_type = CSVContentTypeField(
|
content_type = CSVContentTypeField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
limit_choices_to=FeatureQuery('custom_links'),
|
limit_choices_to=FeatureQuery('custom_links'),
|
||||||
help_text="One or more assigned object types"
|
help_text="Assigned object type"
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -180,6 +180,94 @@ class CustomLinkFilterForm(BootstrapMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Export templates
|
||||||
|
#
|
||||||
|
|
||||||
|
class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||||
|
content_type = ContentTypeChoiceField(
|
||||||
|
queryset=ContentType.objects.all(),
|
||||||
|
limit_choices_to=FeatureQuery('custom_links')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExportTemplate
|
||||||
|
fields = '__all__'
|
||||||
|
fieldsets = (
|
||||||
|
('Custom Link', ('name', 'content_type', 'description')),
|
||||||
|
('Template', ('template_code',)),
|
||||||
|
('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateCSVForm(CSVModelForm):
|
||||||
|
content_type = CSVContentTypeField(
|
||||||
|
queryset=ContentType.objects.all(),
|
||||||
|
limit_choices_to=FeatureQuery('export_templates'),
|
||||||
|
help_text="Assigned object type"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExportTemplate
|
||||||
|
fields = (
|
||||||
|
'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment', 'template_code',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||||
|
pk = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=ExportTemplate.objects.all(),
|
||||||
|
widget=forms.MultipleHiddenInput
|
||||||
|
)
|
||||||
|
content_type = ContentTypeChoiceField(
|
||||||
|
queryset=ContentType.objects.all(),
|
||||||
|
limit_choices_to=FeatureQuery('custom_fields'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
max_length=200,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
mime_type = forms.CharField(
|
||||||
|
max_length=50,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
file_extension = forms.CharField(
|
||||||
|
max_length=15,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
as_attachment = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
widget=BulkEditNullBooleanSelect()
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['description', 'mime_type', 'file_extension']
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateFilterForm(BootstrapMixin, forms.Form):
|
||||||
|
field_groups = [
|
||||||
|
['content_type', 'mime_type'],
|
||||||
|
['file_extension', 'as_attachment'],
|
||||||
|
]
|
||||||
|
content_type = ContentTypeChoiceField(
|
||||||
|
queryset=ContentType.objects.all(),
|
||||||
|
limit_choices_to=FeatureQuery('custom_fields')
|
||||||
|
)
|
||||||
|
mime_type = forms.CharField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
file_extension = forms.CharField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
as_attachment = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
widget=StaticSelect2(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom field models
|
# Custom field models
|
||||||
#
|
#
|
||||||
|
@ -28,4 +28,14 @@ class Migration(migrations.Migration):
|
|||||||
name='last_updated',
|
name='last_updated',
|
||||||
field=models.DateTimeField(auto_now=True, null=True),
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='created',
|
||||||
|
field=models.DateField(auto_now_add=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, null=True),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -171,6 +171,7 @@ class Webhook(BigIDModel):
|
|||||||
# Custom links
|
# Custom links
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@extras_features('webhooks')
|
||||||
class CustomLink(ChangeLoggedModel):
|
class CustomLink(ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
||||||
@ -229,7 +230,8 @@ class CustomLink(ChangeLoggedModel):
|
|||||||
# Export templates
|
# Export templates
|
||||||
#
|
#
|
||||||
|
|
||||||
class ExportTemplate(BigIDModel):
|
@extras_features('webhooks')
|
||||||
|
class ExportTemplate(ChangeLoggedModel):
|
||||||
content_type = models.ForeignKey(
|
content_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -272,6 +274,9 @@ class ExportTemplate(BigIDModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.content_type}: {self.name}"
|
return f"{self.content_type}: {self.name}"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('extras:exporttemplate', args=[self.pk])
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ class CustomLinkTable(BaseTable):
|
|||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
new_window = BooleanColumn()
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = CustomLink
|
model = CustomLink
|
||||||
@ -64,6 +65,27 @@ class CustomLinkTable(BaseTable):
|
|||||||
default_columns = ('pk', 'name', 'content_type', 'group_name', 'button_class', 'new_window')
|
default_columns = ('pk', 'name', 'content_type', 'group_name', 'button_class', 'new_window')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Export templates
|
||||||
|
#
|
||||||
|
|
||||||
|
class ExportTemplateTable(BaseTable):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
as_attachment = BooleanColumn()
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = ExportTemplate
|
||||||
|
fields = (
|
||||||
|
'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
|
@ -86,6 +86,40 @@ class CustomLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||||
|
model = ExportTemplate
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
site_ct = ContentType.objects.get_for_model(Site)
|
||||||
|
TEMPLATE_CODE = """{% for object in queryset %}{{ object }}{% endfor %}"""
|
||||||
|
ExportTemplate.objects.bulk_create((
|
||||||
|
ExportTemplate(name='Export Template 1', content_type=site_ct, template_code=TEMPLATE_CODE),
|
||||||
|
ExportTemplate(name='Export Template 2', content_type=site_ct, template_code=TEMPLATE_CODE),
|
||||||
|
ExportTemplate(name='Export Template 3', content_type=site_ct, template_code=TEMPLATE_CODE),
|
||||||
|
))
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'name': 'Export Template X',
|
||||||
|
'content_type': site_ct.pk,
|
||||||
|
'template_code': TEMPLATE_CODE,
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"name,content_type,template_code",
|
||||||
|
f"Export Template 4,dcim.site,{TEMPLATE_CODE}",
|
||||||
|
f"Export Template 5,dcim.site,{TEMPLATE_CODE}",
|
||||||
|
f"Export Template 6,dcim.site,{TEMPLATE_CODE}",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {
|
||||||
|
'mime_type': 'text/html',
|
||||||
|
'file_extension': 'html',
|
||||||
|
'as_attachment': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||||
model = Tag
|
model = Tag
|
||||||
|
|
||||||
|
@ -30,6 +30,18 @@ urlpatterns = [
|
|||||||
path('custom-links/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='customlink_changelog',
|
path('custom-links/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='customlink_changelog',
|
||||||
kwargs={'model': models.CustomLink}),
|
kwargs={'model': models.CustomLink}),
|
||||||
|
|
||||||
|
# Export templates
|
||||||
|
path('export-templates/', views.ExportTemplateListView.as_view(), name='exporttemplate_list'),
|
||||||
|
path('export-templates/add/', views.ExportTemplateEditView.as_view(), name='exporttemplate_add'),
|
||||||
|
path('export-templates/import/', views.ExportTemplateBulkImportView.as_view(), name='exporttemplate_import'),
|
||||||
|
path('export-templates/edit/', views.ExportTemplateBulkEditView.as_view(), name='exporttemplate_bulk_edit'),
|
||||||
|
path('export-templates/delete/', views.ExportTemplateBulkDeleteView.as_view(), name='exporttemplate_bulk_delete'),
|
||||||
|
path('export-templates/<int:pk>/', views.ExportTemplateView.as_view(), name='exporttemplate'),
|
||||||
|
path('export-templates/<int:pk>/edit/', views.ExportTemplateEditView.as_view(), name='exporttemplate_edit'),
|
||||||
|
path('export-templates/<int:pk>/delete/', views.ExportTemplateDeleteView.as_view(), name='exporttemplate_delete'),
|
||||||
|
path('export-templates/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='exporttemplate_changelog',
|
||||||
|
kwargs={'model': models.ExportTemplate}),
|
||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
path('tags/', views.TagListView.as_view(), name='tag_list'),
|
path('tags/', views.TagListView.as_view(), name='tag_list'),
|
||||||
path('tags/add/', views.TagEditView.as_view(), name='tag_add'),
|
path('tags/add/', views.TagEditView.as_view(), name='tag_add'),
|
||||||
|
@ -106,6 +106,49 @@ class CustomLinkBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.CustomLinkTable
|
table = tables.CustomLinkTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Export templates
|
||||||
|
#
|
||||||
|
|
||||||
|
class ExportTemplateListView(generic.ObjectListView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
filterset = filtersets.ExportTemplateFilterSet
|
||||||
|
filterset_form = forms.ExportTemplateFilterForm
|
||||||
|
table = tables.ExportTemplateTable
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateView(generic.ObjectView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateEditView(generic.ObjectEditView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
model_form = forms.ExportTemplateForm
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
model_form = forms.ExportTemplateCSVForm
|
||||||
|
table = tables.ExportTemplateTable
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
filterset = filtersets.ExportTemplateFilterSet
|
||||||
|
table = tables.ExportTemplateTable
|
||||||
|
form = forms.ExportTemplateBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
filterset = filtersets.ExportTemplateFilterSet
|
||||||
|
table = tables.ExportTemplateTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:customlink_list' %}">Cusotm Links</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:customlink_list' %}">Custom Links</a></li>
|
||||||
<li class="breadcrumb-item">{{ object }}</li>
|
<li class="breadcrumb-item">{{ object }}</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
66
netbox/templates/extras/exporttemplate.html
Normal file
66
netbox/templates/extras/exporttemplate.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'extras:exporttemplate_list' %}">Export Templates</a></li>
|
||||||
|
<li class="breadcrumb-item">{{ object }}</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">
|
||||||
|
Export Template
|
||||||
|
</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">Description</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">MIME Type</th>
|
||||||
|
<td>{{ object.mime_type|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">File Extension</th>
|
||||||
|
<td>{{ object.file_extension|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Attachment</th>
|
||||||
|
<td>
|
||||||
|
{% if object.as_attachment %}
|
||||||
|
<i class="mdi mdi-check-bold text-success" title="Yes"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="mdi mdi-close-thick text-danger" title="No"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">
|
||||||
|
Template
|
||||||
|
</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<pre>{{ object.template_code }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -88,7 +88,7 @@ def export_button(context, content_type=None):
|
|||||||
user = context['request'].user
|
user = context['request'].user
|
||||||
export_templates = ExportTemplate.objects.restrict(user, 'view').filter(content_type=content_type)
|
export_templates = ExportTemplate.objects.restrict(user, 'view').filter(content_type=content_type)
|
||||||
if user.is_staff and user.has_perm('extras.add_exporttemplate'):
|
if user.is_staff and user.has_perm('extras.add_exporttemplate'):
|
||||||
add_exporttemplate_link = f"{reverse('admin:extras_exporttemplate_add')}?content_type={content_type.pk}"
|
add_exporttemplate_link = f"{reverse('extras:exporttemplate_add')}?content_type={content_type.pk}"
|
||||||
else:
|
else:
|
||||||
export_templates = []
|
export_templates = []
|
||||||
|
|
||||||
|
@ -296,6 +296,8 @@ OTHER_MENU = Menu(
|
|||||||
add_url="extras:customfield_add", import_url="extras:customfield_import"),
|
add_url="extras:customfield_add", import_url="extras:customfield_import"),
|
||||||
MenuItem(label="Custom Links", url="extras:customlink_list",
|
MenuItem(label="Custom Links", url="extras:customlink_list",
|
||||||
add_url="extras:customlink_add", import_url="extras:customlink_import"),
|
add_url="extras:customlink_add", import_url="extras:customlink_import"),
|
||||||
|
MenuItem(label="Export Templates", url="extras:exporttemplate_list",
|
||||||
|
add_url="extras:exporttemplate_add", import_url="extras:exporttemplate_import"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MenuGroup(
|
MenuGroup(
|
||||||
|
Reference in New Issue
Block a user