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

Closes #18990: Add description field to ImageAttachment model (#19907)

Jeremy Stretch 7 месяцев назад
Родитель
Сommit
4e0e4598b0

+ 2 - 2
netbox/extras/api/serializers_/attachments.py

@@ -24,10 +24,10 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
     class Meta:
         model = ImageAttachment
         fields = [
-            'id', 'url', 'display', 'object_type', 'object_id', 'parent', 'name', 'image',
+            'id', 'url', 'display', 'object_type', 'object_id', 'parent', 'name', 'image', 'description',
             'image_height', 'image_width', 'created', 'last_updated',
         ]
-        brief_fields = ('id', 'url', 'display', 'name', 'image')
+        brief_fields = ('id', 'url', 'display', 'name', 'image', 'description')
 
     def validate(self, data):
 

+ 5 - 2
netbox/extras/filtersets.py

@@ -451,12 +451,15 @@ class ImageAttachmentFilterSet(ChangeLoggedModelFilterSet):
 
     class Meta:
         model = ImageAttachment
-        fields = ('id', 'object_type_id', 'object_id', 'name', 'image_width', 'image_height')
+        fields = ('id', 'object_type_id', 'object_id', 'name', 'description', 'image_width', 'image_height')
 
     def search(self, queryset, name, value):
         if not value.strip():
             return queryset
-        return queryset.filter(name__icontains=value)
+        return queryset.filter(
+            Q(name__icontains=value) |
+            Q(description__icontains=value)
+        )
 
 
 class JournalEntryFilterSet(NetBoxModelFilterSet):

+ 5 - 2
netbox/extras/forms/model_forms.py

@@ -744,14 +744,17 @@ class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm):
 
 class ImageAttachmentForm(forms.ModelForm):
     fieldsets = (
-        FieldSet(ObjectAttribute('parent'), 'name', 'image'),
+        FieldSet(ObjectAttribute('parent'), 'image', 'name', 'description'),
     )
 
     class Meta:
         model = ImageAttachment
         fields = [
-            'name', 'image',
+            'image', 'name', 'description',
         ]
+        help_texts = {
+            'name': _("If no name is specified, the file name will be used.")
+        }
 
 
 class JournalEntryForm(NetBoxModelForm):

+ 16 - 0
netbox/extras/migrations/0130_imageattachment_description.py

@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0129_fix_script_paths'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='imageattachment',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+    ]

+ 14 - 4
netbox/extras/models/models.py

@@ -1,4 +1,5 @@
 import json
+import os
 import urllib.parse
 
 from django.conf import settings
@@ -678,6 +679,11 @@ class ImageAttachment(ChangeLoggedModel):
         max_length=50,
         blank=True
     )
+    description = models.CharField(
+        verbose_name=_('description'),
+        max_length=200,
+        blank=True
+    )
 
     objects = RestrictedQuerySet.as_manager()
 
@@ -692,10 +698,10 @@ class ImageAttachment(ChangeLoggedModel):
         verbose_name_plural = _('image attachments')
 
     def __str__(self):
-        if self.name:
-            return self.name
-        filename = self.image.name.rsplit('/', 1)[-1]
-        return filename.split('_', 2)[2]
+        return self.name or self.filename
+
+    def get_absolute_url(self):
+        return reverse('extras:imageattachment', args=[self.pk])
 
     def clean(self):
         super().clean()
@@ -719,6 +725,10 @@ class ImageAttachment(ChangeLoggedModel):
         # before the request finishes. (For example, to display a message indicating the ImageAttachment was deleted.)
         self.image.name = _name
 
+    @property
+    def filename(self):
+        return os.path.basename(self.image.name).split('_', 2)[2]
+
     @property
     def size(self):
         """

+ 10 - 0
netbox/extras/search.py

@@ -14,6 +14,16 @@ class CustomFieldIndex(SearchIndex):
     display_attrs = ('description',)
 
 
+@register_search
+class ImageAttachmentIndex(SearchIndex):
+    model = models.ImageAttachment
+    fields = (
+        ('name', 100),
+        ('description', 500),
+    )
+    display_attrs = ('description',)
+
+
 @register_search
 class JournalEntryIndex(SearchIndex):
     model = models.JournalEntry

+ 3 - 3
netbox/extras/tables/tables.py

@@ -249,10 +249,10 @@ class ImageAttachmentTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = ImageAttachment
         fields = (
-            'pk', 'object_type', 'parent', 'image', 'name', 'image_height', 'image_width', 'size', 'created',
-            'last_updated',
+            'pk', 'object_type', 'parent', 'image', 'name', 'description', 'image_height', 'image_width', 'size',
+            'created', 'last_updated',
         )
-        default_columns = ('object_type', 'parent', 'image', 'name', 'size', 'created')
+        default_columns = ('object_type', 'parent', 'image', 'name', 'description', 'size', 'created')
 
 
 class SavedFilterTable(NetBoxTable):

+ 1 - 1
netbox/extras/tests/test_api.py

@@ -579,7 +579,7 @@ class ImageAttachmentTest(
     APIViewTestCases.GraphQLTestCase
 ):
     model = ImageAttachment
-    brief_fields = ['display', 'id', 'image', 'name', 'url']
+    brief_fields = ['description', 'display', 'id', 'image', 'name', 'url']
 
     @classmethod
     def setUpTestData(cls):

+ 5 - 6
netbox/extras/views.py

@@ -1040,6 +1040,11 @@ class ImageAttachmentListView(generic.ObjectListView):
     actions = (BulkExport,)
 
 
+@register_model_view(ImageAttachment)
+class ImageAttachmentView(generic.ObjectView):
+    queryset = ImageAttachment.objects.all()
+
+
 @register_model_view(ImageAttachment, 'add', detail=False)
 @register_model_view(ImageAttachment, 'edit')
 class ImageAttachmentEditView(generic.ObjectEditView):
@@ -1053,9 +1058,6 @@ class ImageAttachmentEditView(generic.ObjectEditView):
             instance.parent = get_object_or_404(object_type.model_class(), pk=request.GET.get('object_id'))
         return instance
 
-    def get_return_url(self, request, obj=None):
-        return obj.parent.get_absolute_url() if obj else super().get_return_url(request)
-
     def get_extra_addanother_params(self, request):
         return {
             'object_type': request.GET.get('object_type'),
@@ -1067,9 +1069,6 @@ class ImageAttachmentEditView(generic.ObjectEditView):
 class ImageAttachmentDeleteView(generic.ObjectDeleteView):
     queryset = ImageAttachment.objects.all()
 
-    def get_return_url(self, request, obj=None):
-        return obj.parent.get_absolute_url() if obj else super().get_return_url(request)
-
 
 #
 # Journal entries

+ 64 - 1
netbox/templates/extras/imageattachment.html

@@ -1,4 +1,67 @@
 {% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+{% load i18n %}
 
-{% block tabs %}
+{% block content %}
+  <div class="row">
+    <div class="col col-12 col-md-6">
+      <div class="card">
+        <h2 class="card-header">{% trans "Image Attachment" %}</h2>
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">{% trans "Parent Object" %}</th>
+            <td>{{ object.parent|linkify }}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Name" %}</th>
+            <td>{{ object.name|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Description" %}</th>
+            <td>{{ object.description|placeholder }}</td>
+          </tr>
+        </table>
+      </div>
+      {% plugin_left_page object %}
+    </div>
+    <div class="col col-12 col-md-6">
+      <div class="card">
+        <h2 class="card-header">{% trans "File" %}</h2>
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">{% trans "Filename" %}</th>
+            <td>
+              <a href="{{ object.image.url }}" target="_blank">{{ object.filename }}</a>
+              <i class="mdi mdi-open-in-new"></i>
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Dimensions" %}</th>
+            <td>{{ object.image_width }} × {{ object.image_height }}</td>
+          </tr>
+          <tr>
+            <th scope="row">{% trans "Size" %}</th>
+            <td>
+              <span title="{{ object.size }} {% trans "bytes" %}">{{ object.size|filesizeformat }}</span>
+            </td>
+          </tr>
+        </table>
+      </div>
+      {% plugin_right_page object %}
+    </div>
+  </div>
+  <div class="row">
+    <div class="col col-md-12">
+      <div class="card">
+        <h2 class="card-header">{% trans "Image" %}</h2>
+        <div class="card-body">
+          <a href="{{ object.image.url }}">
+            <img src="{{ object.image.url }}" height="{{ image.height }}" width="{{ image.width }}" alt="{{ object }}" />
+          </a>
+        </div>
+      </div>
+      {% plugin_full_width_page object %}
+    </div>
+  </div>
 {% endblock %}