mirror of
https://github.com/netbox-community/netbox.git
synced 2025-09-06 14:23:36 -06:00
* Initial work on #19591 * Ignore images cache directory * Clean up thumbnails layout * Include "add attachment" button * Clean up ObjectImageAttachmentsView * Add html_tag property to ImageAttachment * Misc cleanup * Collapse .gitignore files for /media * Fix conditional in template
This commit is contained in:
parent
40dd36812c
commit
9a2fab1d48
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ yarn-error.log*
|
|||||||
/netbox/netbox/configuration.py
|
/netbox/netbox/configuration.py
|
||||||
/netbox/netbox/ldap_config.py
|
/netbox/netbox/ldap_config.py
|
||||||
/netbox/local/*
|
/netbox/local/*
|
||||||
|
/netbox/media
|
||||||
/netbox/reports/*
|
/netbox/reports/*
|
||||||
!/netbox/reports/__init__.py
|
!/netbox/reports/__init__.py
|
||||||
/netbox/scripts/*
|
/netbox/scripts/*
|
||||||
|
@ -141,6 +141,10 @@ social-auth-app-django
|
|||||||
# https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md
|
# https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md
|
||||||
social-auth-core
|
social-auth-core
|
||||||
|
|
||||||
|
# Image thumbnail generation
|
||||||
|
# https://github.com/jazzband/sorl-thumbnail/blob/master/CHANGES.rst
|
||||||
|
sorl-thumbnail
|
||||||
|
|
||||||
# Strawberry GraphQL
|
# Strawberry GraphQL
|
||||||
# https://github.com/strawberry-graphql/strawberry/blob/main/CHANGELOG.md
|
# https://github.com/strawberry-graphql/strawberry/blob/main/CHANGELOG.md
|
||||||
strawberry-graphql
|
strawberry-graphql
|
||||||
|
@ -9,6 +9,8 @@ from django.core.validators import ValidationError
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.utils.encoders import JSONEncoder
|
from rest_framework.utils.encoders import JSONEncoder
|
||||||
|
|
||||||
@ -728,6 +730,18 @@ class ImageAttachment(ChangeLoggedModel):
|
|||||||
def filename(self):
|
def filename(self):
|
||||||
return os.path.basename(self.image.name).split('_', 2)[2]
|
return os.path.basename(self.image.name).split('_', 2)[2]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def html_tag(self):
|
||||||
|
"""
|
||||||
|
Returns a complete <img> tag suitable for embedding in an HTML document.
|
||||||
|
"""
|
||||||
|
return mark_safe('<img src="{url}" height="{height}" width="{width}" alt="{alt_text}" />'.format(
|
||||||
|
url=self.image.url,
|
||||||
|
height=self.image_height,
|
||||||
|
width=self.image_width,
|
||||||
|
alt_text=escape(self.description or self.name),
|
||||||
|
))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
"""
|
"""
|
||||||
|
2
netbox/media/devicetype-images/.gitignore
vendored
2
netbox/media/devicetype-images/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
2
netbox/media/image-attachments/.gitignore
vendored
2
netbox/media/image-attachments/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
@ -736,6 +736,10 @@ def register_models(*models):
|
|||||||
register_model_view(model, 'jobs', kwargs={'model': model})(
|
register_model_view(model, 'jobs', kwargs={'model': model})(
|
||||||
'netbox.views.generic.ObjectJobsView'
|
'netbox.views.generic.ObjectJobsView'
|
||||||
)
|
)
|
||||||
|
if issubclass(model, ImageAttachmentsMixin):
|
||||||
|
register_model_view(model, 'image-attachments', kwargs={'model': model})(
|
||||||
|
'netbox.views.generic.ObjectImageAttachmentsView'
|
||||||
|
)
|
||||||
if issubclass(model, SyncedDataMixin):
|
if issubclass(model, SyncedDataMixin):
|
||||||
register_model_view(model, 'sync', kwargs={'model': model})(
|
register_model_view(model, 'sync', kwargs={'model': model})(
|
||||||
'netbox.views.generic.ObjectSyncDataView'
|
'netbox.views.generic.ObjectSyncDataView'
|
||||||
|
@ -424,6 +424,7 @@ INSTALLED_APPS = [
|
|||||||
'mptt',
|
'mptt',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'social_django',
|
'social_django',
|
||||||
|
'sorl.thumbnail',
|
||||||
'taggit',
|
'taggit',
|
||||||
'timezone_field',
|
'timezone_field',
|
||||||
'core',
|
'core',
|
||||||
|
@ -10,7 +10,7 @@ from django.views.generic import View
|
|||||||
from core.models import Job, ObjectChange
|
from core.models import Job, ObjectChange
|
||||||
from core.tables import JobTable, ObjectChangeTable
|
from core.tables import JobTable, ObjectChangeTable
|
||||||
from extras.forms import JournalEntryForm
|
from extras.forms import JournalEntryForm
|
||||||
from extras.models import JournalEntry
|
from extras.models import ImageAttachment, JournalEntry
|
||||||
from extras.tables import JournalEntryTable
|
from extras.tables import JournalEntryTable
|
||||||
from tenancy.models import ContactAssignment
|
from tenancy.models import ContactAssignment
|
||||||
from tenancy.tables import ContactAssignmentTable
|
from tenancy.tables import ContactAssignmentTable
|
||||||
@ -25,6 +25,7 @@ __all__ = (
|
|||||||
'BulkSyncDataView',
|
'BulkSyncDataView',
|
||||||
'ObjectChangeLogView',
|
'ObjectChangeLogView',
|
||||||
'ObjectContactsView',
|
'ObjectContactsView',
|
||||||
|
'ObjectImageAttachmentsView',
|
||||||
'ObjectJobsView',
|
'ObjectJobsView',
|
||||||
'ObjectJournalView',
|
'ObjectJournalView',
|
||||||
'ObjectSyncDataView',
|
'ObjectSyncDataView',
|
||||||
@ -84,6 +85,41 @@ class ObjectChangeLogView(ConditionalLoginRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectImageAttachmentsView(ConditionalLoginRequiredMixin, View):
|
||||||
|
"""
|
||||||
|
Render all images attached to the object as linked thumbnails.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used.
|
||||||
|
"""
|
||||||
|
base_template = None
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Images'),
|
||||||
|
badge=lambda obj: obj.images.count(),
|
||||||
|
permission='extras.view_imageattachment',
|
||||||
|
weight=6000
|
||||||
|
)
|
||||||
|
|
||||||
|
def get(self, request, model, **kwargs):
|
||||||
|
obj = get_object_or_404(model.objects.restrict(request.user, 'view'), **kwargs)
|
||||||
|
image_attachments = ImageAttachment.objects.filter(
|
||||||
|
object_type=ContentType.objects.get_for_model(obj),
|
||||||
|
object_id=obj.pk,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
|
||||||
|
# fall back to using base.html.
|
||||||
|
if self.base_template is None:
|
||||||
|
self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
|
||||||
|
|
||||||
|
return render(request, 'extras/object_imageattachments.html', {
|
||||||
|
'object': obj,
|
||||||
|
'image_attachments': image_attachments,
|
||||||
|
'base_template': self.base_template,
|
||||||
|
'tab': self.tab,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class ObjectJournalView(ConditionalLoginRequiredMixin, View):
|
class ObjectJournalView(ConditionalLoginRequiredMixin, View):
|
||||||
"""
|
"""
|
||||||
Show all journal entries for an object. The model class must be passed as a keyword argument when referencing this
|
Show all journal entries for an object. The model class must be passed as a keyword argument when referencing this
|
||||||
|
BIN
netbox/project-static/dist/netbox.css
vendored
BIN
netbox/project-static/dist/netbox.css
vendored
Binary file not shown.
@ -81,6 +81,14 @@ img.plugin-icon {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Image attachment thumbnails
|
||||||
|
.thumbnail {
|
||||||
|
max-width: 200px;
|
||||||
|
img {
|
||||||
|
border: 1px solid #606060;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body[data-bs-theme=dark] {
|
body[data-bs-theme=dark] {
|
||||||
// Assuming icon is black/white line art, invert it and tone down brightness
|
// Assuming icon is black/white line art, invert it and tone down brightness
|
||||||
img.plugin-icon {
|
img.plugin-icon {
|
||||||
|
@ -56,8 +56,8 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Image" %}</h2>
|
<h2 class="card-header">{% trans "Image" %}</h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a href="{{ object.image.url }}">
|
<a href="{{ object.image.url }}" title="{{ object.name }}">
|
||||||
<img src="{{ object.image.url }}" height="{{ image.height }}" width="{{ image.width }}" alt="{{ object }}" />
|
{{ object.html_tag }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
46
netbox/templates/extras/object_imageattachments.html
Normal file
46
netbox/templates/extras/object_imageattachments.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends base_template %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load thumbnail %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{% if perms.extras.add_imageattachment %}
|
||||||
|
{% with viewname=object|viewname:"image-attachments" %}
|
||||||
|
<a href="{% url 'extras:imageattachment_add' %}?object_type={{ object|content_type_id }}&object_id={{ object.pk }}&return_url={% url viewname pk=object.pk %}" class="btn btn-primary">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Attach an Image" %}
|
||||||
|
</a>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if image_attachments %}
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
|
{% for object in image_attachments %}
|
||||||
|
<div class="thumbnail m-2">
|
||||||
|
{% thumbnail object.image "200x200" crop="center" as tn %}
|
||||||
|
<a href="{{ object.get_absolute_url }}" class="d-block" title="{{ object.name }}">
|
||||||
|
<img
|
||||||
|
src="{{ tn.url }}"
|
||||||
|
width="{{ tn.width }}"
|
||||||
|
height="{{ tn.height }}"
|
||||||
|
class="rounded"
|
||||||
|
alt="{{ object.description|default:object.name }}"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{% endthumbnail %}
|
||||||
|
<div class="text-center text-secondary text-truncate fs-5">
|
||||||
|
{{ object }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% blocktrans with object_type=object|meta:"verbose_name" %}
|
||||||
|
No images have been attached to this {{ object_type }}.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -33,6 +33,7 @@ requests==2.32.4
|
|||||||
rq==2.4.1
|
rq==2.4.1
|
||||||
social-auth-app-django==5.5.1
|
social-auth-app-django==5.5.1
|
||||||
social-auth-core==4.7.0
|
social-auth-core==4.7.0
|
||||||
|
sorl-thumbnail==12.11.0
|
||||||
strawberry-graphql==0.278.0
|
strawberry-graphql==0.278.0
|
||||||
strawberry-graphql-django==0.65.1
|
strawberry-graphql-django==0.65.1
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
|
Loading…
Reference in New Issue
Block a user