1
0
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:
jeremystretch
2021-06-23 20:39:35 -04:00
parent 276ded0119
commit 10cbbee947
12 changed files with 288 additions and 41 deletions

View File

@ -2,8 +2,8 @@ from django import forms
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from utilities.forms import ContentTypeChoiceField, ContentTypeMultipleChoiceField, LaxURLField
from .models import CustomLink, ExportTemplate, JobResult, Webhook
from utilities.forms import ContentTypeMultipleChoiceField, LaxURLField
from .models import JobResult, Webhook
from .utils import FeatureQuery
@ -57,41 +57,6 @@ class WebhookAdmin(admin.ModelAdmin):
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
#

View File

@ -123,7 +123,7 @@ class CustomLinkCSVForm(CSVModelForm):
content_type = CSVContentTypeField(
queryset=ContentType.objects.all(),
limit_choices_to=FeatureQuery('custom_links'),
help_text="One or more assigned object types"
help_text="Assigned object type"
)
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
#

View File

@ -28,4 +28,14 @@ class Migration(migrations.Migration):
name='last_updated',
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),
),
]

View File

@ -171,6 +171,7 @@ class Webhook(BigIDModel):
# Custom links
#
@extras_features('webhooks')
class CustomLink(ChangeLoggedModel):
"""
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
#
class ExportTemplate(BigIDModel):
@extras_features('webhooks')
class ExportTemplate(ChangeLoggedModel):
content_type = models.ForeignKey(
to=ContentType,
on_delete=models.CASCADE,
@ -272,6 +274,9 @@ class ExportTemplate(BigIDModel):
def __str__(self):
return f"{self.content_type}: {self.name}"
def get_absolute_url(self):
return reverse('extras:exporttemplate', args=[self.pk])
def clean(self):
super().clean()

View File

@ -55,6 +55,7 @@ class CustomLinkTable(BaseTable):
name = tables.Column(
linkify=True
)
new_window = BooleanColumn()
class Meta(BaseTable.Meta):
model = CustomLink
@ -64,6 +65,27 @@ class CustomLinkTable(BaseTable):
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
#

View File

@ -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):
model = Tag

View File

@ -30,6 +30,18 @@ urlpatterns = [
path('custom-links/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='customlink_changelog',
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
path('tags/', views.TagListView.as_view(), name='tag_list'),
path('tags/add/', views.TagEditView.as_view(), name='tag_add'),

View File

@ -106,6 +106,49 @@ class CustomLinkBulkDeleteView(generic.BulkDeleteView):
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
#

View File

@ -3,7 +3,7 @@
{% load plugins %}
{% 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>
{% endblock %}

View 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 %}

View File

@ -88,7 +88,7 @@ def export_button(context, content_type=None):
user = context['request'].user
export_templates = ExportTemplate.objects.restrict(user, 'view').filter(content_type=content_type)
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:
export_templates = []

View File

@ -296,6 +296,8 @@ OTHER_MENU = Menu(
add_url="extras:customfield_add", import_url="extras:customfield_import"),
MenuItem(label="Custom Links", url="extras:customlink_list",
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(