diff --git a/netbox/extras/api/serializers_/attachments.py b/netbox/extras/api/serializers_/attachments.py
index fe0964eae..6507a12be 100644
--- a/netbox/extras/api/serializers_/attachments.py
+++ b/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):
diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py
index 6fa81f8d3..a1ead5494 100644
--- a/netbox/extras/filtersets.py
+++ b/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):
diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py
index 5590dfa1a..fd333322b 100644
--- a/netbox/extras/forms/model_forms.py
+++ b/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):
diff --git a/netbox/extras/migrations/0130_imageattachment_description.py b/netbox/extras/migrations/0130_imageattachment_description.py
new file mode 100644
index 000000000..1e6dd8867
--- /dev/null
+++ b/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),
+ ),
+ ]
diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py
index aa5af892f..2fdc1ffe3 100644
--- a/netbox/extras/models/models.py
+++ b/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):
"""
diff --git a/netbox/extras/search.py b/netbox/extras/search.py
index feb235c29..bc6381512 100644
--- a/netbox/extras/search.py
+++ b/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
diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py
index e6f488fde..1c512f408 100644
--- a/netbox/extras/tables/tables.py
+++ b/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):
diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py
index 16e95703e..b5a6cb018 100644
--- a/netbox/extras/tests/test_api.py
+++ b/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):
diff --git a/netbox/extras/views.py b/netbox/extras/views.py
index 43172139c..a2664a2c2 100644
--- a/netbox/extras/views.py
+++ b/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
diff --git a/netbox/templates/extras/imageattachment.html b/netbox/templates/extras/imageattachment.html
index 1968344cc..9d0a4e479 100644
--- a/netbox/templates/extras/imageattachment.html
+++ b/netbox/templates/extras/imageattachment.html
@@ -1,4 +1,67 @@
{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+{% load i18n %}
-{% block tabs %}
+{% block content %}
+
+
+
+
+
+
+ {% trans "Parent Object" %} |
+ {{ object.parent|linkify }} |
+
+
+ {% trans "Name" %} |
+ {{ object.name|placeholder }} |
+
+
+ {% trans "Description" %} |
+ {{ object.description|placeholder }} |
+
+
+
+ {% plugin_left_page object %}
+
+
+
+
+
+
+ {% trans "Filename" %} |
+
+ {{ object.filename }}
+
+ |
+
+
+ {% trans "Dimensions" %} |
+ {{ object.image_width }} × {{ object.image_height }} |
+
+
+ {% trans "Size" %} |
+
+ {{ object.size|filesizeformat }}
+ |
+
+
+
+ {% plugin_right_page object %}
+
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
{% endblock %}