Просмотр исходного кода

Add UI views for export templates

jeremystretch 4 лет назад
Родитель
Сommit
10cbbee947

+ 2 - 37
netbox/extras/admin.py

@@ -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
 #

+ 89 - 1
netbox/extras/forms.py

@@ -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
 #

+ 10 - 0
netbox/extras/migrations/0061_extras_change_logging.py

@@ -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),
+        ),
     ]

+ 6 - 1
netbox/extras/models/models.py

@@ -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()
 

+ 22 - 0
netbox/extras/tables.py

@@ -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
 #

+ 34 - 0
netbox/extras/tests/test_views.py

@@ -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
 

+ 12 - 0
netbox/extras/urls.py

@@ -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'),

+ 43 - 0
netbox/extras/views.py

@@ -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
 #

+ 1 - 1
netbox/templates/extras/customlink.html

@@ -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 - 0
netbox/templates/extras/exporttemplate.html

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

+ 1 - 1
netbox/utilities/templatetags/buttons.py

@@ -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 = []
 

+ 2 - 0
netbox/utilities/templatetags/nav.py

@@ -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(