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.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
|
||||
#
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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 %}
|
||||
|
||||
|
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
|
||||
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 = []
|
||||
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user