Adds image attachment list view (#12487)

* adds image attachment list view #11932

* fixed typo

* Update netbox/extras/tables/tables.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* Update netbox/extras/forms/filtersets.py

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>

* changes as per review

* Disable ordering by size (not stored in database)

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Abhimanyu Saharan 2023-05-05 06:48:13 -07:00 committed by GitHub
parent 9909213c0d
commit 42346702a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 52 additions and 39 deletions

View File

@ -11,7 +11,7 @@ from extras.utils import FeatureQuery
from netbox.forms.base import NetBoxModelFilterSetForm from netbox.forms.base import NetBoxModelFilterSetForm
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField from utilities.forms.fields import ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.widgets import APISelectMultiple, DateTimePicker from utilities.forms.widgets import APISelectMultiple, DateTimePicker
from virtualization.models import Cluster, ClusterGroup, ClusterType from virtualization.models import Cluster, ClusterGroup, ClusterType
from .mixins import SavedFiltersMixin from .mixins import SavedFiltersMixin
@ -22,6 +22,7 @@ __all__ = (
'CustomFieldFilterForm', 'CustomFieldFilterForm',
'CustomLinkFilterForm', 'CustomLinkFilterForm',
'ExportTemplateFilterForm', 'ExportTemplateFilterForm',
'ImageAttachmentFilterForm',
'JournalEntryFilterForm', 'JournalEntryFilterForm',
'LocalConfigContextFilterForm', 'LocalConfigContextFilterForm',
'ObjectChangeFilterForm', 'ObjectChangeFilterForm',
@ -137,6 +138,20 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
) )
class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = (
(None, ('q', 'filter_id')),
('Attributes', ('content_type_id', 'name',)),
)
content_type_id = ContentTypeChoiceField(
queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
required=False
)
name = forms.CharField(
required=False
)
class SavedFilterFilterForm(SavedFiltersMixin, FilterForm): class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id')), (None, ('q', 'filter_id')),

View File

@ -13,6 +13,7 @@ __all__ = (
'CustomFieldTable', 'CustomFieldTable',
'CustomLinkTable', 'CustomLinkTable',
'ExportTemplateTable', 'ExportTemplateTable',
'ImageAttachmentTable',
'JournalEntryTable', 'JournalEntryTable',
'ObjectChangeTable', 'ObjectChangeTable',
'SavedFilterTable', 'SavedFilterTable',
@ -86,6 +87,28 @@ class ExportTemplateTable(NetBoxTable):
) )
class ImageAttachmentTable(NetBoxTable):
id = tables.Column(
linkify=False
)
content_type = columns.ContentTypeColumn()
parent = tables.Column(
linkify=True
)
size = tables.Column(
orderable=False,
verbose_name='Size (bytes)'
)
class Meta(NetBoxTable.Meta):
model = ImageAttachment
fields = (
'pk', 'content_type', 'parent', 'image', 'name', 'image_height', 'image_width', 'size', 'created',
'last_updated',
)
default_columns = ('content_type', 'parent', 'image', 'name', 'size', 'created')
class SavedFilterTable(NetBoxTable): class SavedFilterTable(NetBoxTable):
name = tables.Column( name = tables.Column(
linkify=True linkify=True

View File

@ -73,6 +73,7 @@ urlpatterns = [
path('config-templates/<int:pk>/', include(get_model_urls('extras', 'configtemplate'))), path('config-templates/<int:pk>/', include(get_model_urls('extras', 'configtemplate'))),
# Image attachments # Image attachments
path('image-attachments/', views.ImageAttachmentListView.as_view(), name='imageattachment_list'),
path('image-attachments/add/', views.ImageAttachmentEditView.as_view(), name='imageattachment_add'), path('image-attachments/add/', views.ImageAttachmentEditView.as_view(), name='imageattachment_add'),
path('image-attachments/<int:pk>/', include(get_model_urls('extras', 'imageattachment'))), path('image-attachments/<int:pk>/', include(get_model_urls('extras', 'imageattachment'))),

View File

@ -577,6 +577,14 @@ class ObjectChangeView(generic.ObjectView):
# Image attachments # Image attachments
# #
class ImageAttachmentListView(generic.ObjectListView):
queryset = ImageAttachment.objects.all()
filterset = filtersets.ImageAttachmentFilterSet
filterset_form = forms.ImageAttachmentFilterForm
table = tables.ImageAttachmentTable
actions = ('export',)
@register_model_view(ImageAttachment, 'edit') @register_model_view(ImageAttachment, 'edit')
class ImageAttachmentEditView(generic.ObjectEditView): class ImageAttachmentEditView(generic.ObjectEditView):
queryset = ImageAttachment.objects.all() queryset = ImageAttachment.objects.all()

View File

@ -292,6 +292,7 @@ CUSTOMIZATION_MENU = Menu(
get_model_item('extras', 'exporttemplate', _('Export Templates')), get_model_item('extras', 'exporttemplate', _('Export Templates')),
get_model_item('extras', 'savedfilter', _('Saved Filters')), get_model_item('extras', 'savedfilter', _('Saved Filters')),
get_model_item('extras', 'tag', 'Tags'), get_model_item('extras', 'tag', 'Tags'),
get_model_item('extras', 'imageattachment', _('Image Attachments'), actions=()),
), ),
), ),
MenuGroup( MenuGroup(

View File

@ -4,44 +4,9 @@
<h5 class="card-header"> <h5 class="card-header">
Images Images
</h5> </h5>
<div class="card-body"> <div class="card-body htmx-container table-responsive"
{% with images=object.images.all %} hx-get="{% url 'extras:imageattachment_list' %}?content_type_id={{ object|content_type_id }}&object_id={{ object.pk }}"
{% if images.exists %} hx-trigger="load"></div>
<table class="table table-hover">
<tr>
<th>Name</th>
<th>Size</th>
<th>Created</th>
<th></th>
</tr>
{% for attachment in images %}
<tr{% if not attachment.size %} class="table-danger"{% endif %}>
<td>
<i class="mdi mdi-file-image-outline"></i>
<a class="image-preview" href="{{ attachment.image.url }}" target="_blank">{{ attachment }}</a>
</td>
<td>{{ attachment.size|filesizeformat }}</td>
<td>{{ attachment.created|annotated_date }}</td>
<td class="text-end noprint">
{% if perms.extras.change_imageattachment %}
<a href="{% url 'extras:imageattachment_edit' pk=attachment.pk %}" class="btn btn-warning btn-sm lh-1" title="Edit Image">
<i class="mdi mdi-pencil" aria-hidden="true"></i>
</a>
{% endif %}
{% if perms.extras.delete_imageattachment %}
<a href="{% url 'extras:imageattachment_delete' pk=attachment.pk %}" class="btn btn-danger btn-sm lh-1" title="Delete Image">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="text-muted">None</div>
{% endif %}
{% endwith %}
</div>
{% if perms.extras.add_imageattachment %} {% if perms.extras.add_imageattachment %}
<div class="card-footer text-end noprint"> <div class="card-footer text-end noprint">
<a href="{% url 'extras:imageattachment_add' %}?content_type={{ object|content_type_id }}&object_id={{ object.pk }}" class="btn btn-primary btn-sm"> <a href="{% url 'extras:imageattachment_add' %}?content_type={{ object|content_type_id }}&object_id={{ object.pk }}" class="btn btn-primary btn-sm">