Compare commits
1
Commits
f7c9ce995f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbb8128385 |
@@ -348,6 +348,12 @@ none are saved. An optional shared description can be applied; the original
|
||||
file name is kept as the display name. Requires permission to add image
|
||||
attachments and to view the target object.
|
||||
|
||||
NetBox caps Pillow at 25 megapixels, which breaks current 50 MP smartphone
|
||||
photos (e.g. Google Pixel, 8160×6144) and their thumbnails. The bulk upload
|
||||
therefore scales larger images down to NetBox' limit (EXIF orientation is
|
||||
applied) before saving. If an image is rejected, the form also shows Pillow's
|
||||
exact error message.
|
||||
|
||||
### Installing multiple modules
|
||||
|
||||
Under **Plugins > NetBox Utilities > Install multiple modules** (or via the
|
||||
@@ -359,17 +365,7 @@ The operation is atomic: if a bay is taken meanwhile or a naming conflict
|
||||
occurs, nothing is saved. Requires the permission to add modules; object
|
||||
permissions still apply.
|
||||
|
||||
<<<<<<< HEAD
|
||||
NetBox begrenzt Pillow global auf 25 Megapixel und lehnt Bilder ab 50
|
||||
Megapixeln ab. Damit aktuelle 50-MP-Smartphone-Fotos (z. B. Google Pixel,
|
||||
8160×6144) hochgeladen werden können, hebt der Mehrfach-Upload dieses Limit
|
||||
während der Verarbeitung auf 100 Megapixel an. Wird ein Bild abgelehnt, zeigt
|
||||
das Formular zusätzlich die genaue Fehlermeldung von Pillow an.
|
||||
|
||||
### Module mehrfach einbauen
|
||||
=======
|
||||
### Personalised navigation
|
||||
>>>>>>> a85e4d6375a673a333d601fa8867e00a96423fec
|
||||
|
||||
Under **Plugins > NetBox Utilities > Customize navigation** users see every menu
|
||||
they have access to. Arrow buttons change the order; a toggle hides a menu.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from netbox.plugins import PluginConfig, get_plugin_config
|
||||
|
||||
__version__ = "0.14.1"
|
||||
__version__ = "0.14.2"
|
||||
|
||||
|
||||
class NetBoxUtilitiesConfig(PluginConfig):
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import SuspiciousFileOperation, ValidationError
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db import DatabaseError, transaction
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
@@ -66,11 +67,45 @@ def raised_image_pixel_limit():
|
||||
if original_limit is not None and original_limit < BULK_IMAGE_MAX_PIXELS:
|
||||
Image.MAX_IMAGE_PIXELS = BULK_IMAGE_MAX_PIXELS
|
||||
try:
|
||||
yield
|
||||
yield original_limit
|
||||
finally:
|
||||
Image.MAX_IMAGE_PIXELS = original_limit
|
||||
|
||||
|
||||
def downscale_image(upload, max_pixels):
|
||||
"""Shrink an upload to NetBox' pixel limit so NetBox can render thumbnails later.
|
||||
|
||||
Returns the original upload when it is small enough or cannot be processed;
|
||||
NetBox' own validation reports broken files afterwards.
|
||||
"""
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
if not max_pixels:
|
||||
return upload
|
||||
try:
|
||||
upload.seek(0)
|
||||
with Image.open(upload) as image:
|
||||
width, height = image.size
|
||||
if width * height <= max_pixels:
|
||||
return upload
|
||||
image_format = image.format
|
||||
image = ImageOps.exif_transpose(image)
|
||||
scale = (max_pixels / (width * height)) ** 0.5
|
||||
size = (max(1, int(image.width * scale)), max(1, int(image.height * scale)))
|
||||
image = image.resize(size, Image.Resampling.LANCZOS)
|
||||
if image_format == "JPEG" and image.mode not in ("RGB", "L"):
|
||||
image = image.convert("RGB")
|
||||
content = BytesIO()
|
||||
save_options = {"quality": 90} if image_format in ("JPEG", "WEBP") else {}
|
||||
image.save(content, format=image_format, **save_options)
|
||||
except Exception: # noqa: BLE001 - leave validation to NetBox
|
||||
return upload
|
||||
finally:
|
||||
if hasattr(upload, "seek"):
|
||||
upload.seek(0)
|
||||
return SimpleUploadedFile(upload.name, content.getvalue(), content_type=getattr(upload, "content_type", None))
|
||||
|
||||
|
||||
class BulkImageUploadView(ContentTypePermissionRequiredMixin, View):
|
||||
template_name = "netbox_utilities/bulk_image_upload.html"
|
||||
|
||||
@@ -86,12 +121,12 @@ class BulkImageUploadView(ContentTypePermissionRequiredMixin, View):
|
||||
parent = self._get_parent(request, request.POST)
|
||||
form = BulkImageUploadForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
with raised_image_pixel_limit():
|
||||
return self._process_upload(request, form, parent)
|
||||
with raised_image_pixel_limit() as netbox_pixel_limit:
|
||||
return self._process_upload(request, form, parent, netbox_pixel_limit)
|
||||
return self._render(request, form, parent)
|
||||
|
||||
def _process_upload(self, request, form, parent):
|
||||
image_forms = self._build_image_forms(parent, form.cleaned_data)
|
||||
def _process_upload(self, request, form, parent, max_pixels):
|
||||
image_forms = self._build_image_forms(parent, form.cleaned_data, max_pixels)
|
||||
invalid = [image_form for image_form in image_forms if not image_form.is_valid()]
|
||||
if invalid:
|
||||
for image_form in invalid:
|
||||
@@ -161,9 +196,10 @@ class BulkImageUploadView(ContentTypePermissionRequiredMixin, View):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _build_image_forms(parent, cleaned_data):
|
||||
def _build_image_forms(parent, cleaned_data, max_pixels=None):
|
||||
image_forms = []
|
||||
for image in cleaned_data["images"]:
|
||||
image = downscale_image(image, max_pixels)
|
||||
if hasattr(image, "seek"):
|
||||
image.seek(0)
|
||||
image_forms.append(
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "netbox-utilities"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6/4.7"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
Reference in New Issue
Block a user