diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f508c25 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,29 @@ +**/__pycache__ +**/.venv +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE + +# Part of Setup.py and therefore important for plugin installation. +# README.md \ No newline at end of file diff --git a/.github/workflows/pub-pypi.yml b/.github/workflows/pub-pypi.yml index ebabf78..f06738b 100644 --- a/.github/workflows/pub-pypi.yml +++ b/.github/workflows/pub-pypi.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@main - - name: Set up Python 3.7 + - name: Set up Python 3.12 uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.12 - name: Install pypa/build run: >- python -m diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2324b25 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +// Is required for debugging Docker-Compose in VSCode. +{ + "configurations": [ + { + + "name": "Docker: Python - Django", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + }, + + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/source" + } + ], + + "django": true, + + "justMyCode": true, + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..734d611 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,19 @@ +// Is required for debugging Docker-Compose in VSCode. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "docker-run: debug", + "type": "docker-compose", + "dockerCompose": { + "up": { + "detached": true, + "build": true + }, + "files": [ + "${workspaceFolder}/develop/docker-compose.yml", + ] + } + } + ] + } diff --git a/CODEOWNERS b/CODEOWNERS index 1d3b141..eeda945 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @k01ek @cruse1977 @natm +* @k01ek @cruse1977 @cvitan diff --git a/Makefile b/Makefile index 414332d..c15f746 100644 --- a/Makefile +++ b/Makefile @@ -1,46 +1,70 @@ +# General: The Makefile is used for plugin development is used to simplify the execution of command sequences. +# Recommended VSCode plugin: Makefile-Tools (extension ID: ms-vscode.makefile-tools) +# To trigger a Makefile command (target), the following line must be written in the terminal window of VSCode, for example: +# make cbuild + +# Parameter PYTHON_VER?=3.12 -NETBOX_VER?=v4.0.2 +NETBOX_VER?=v4.3.1 NAME=netbox-qrcode COMPOSE_FILE=./develop/docker-compose.yml +COMPOSE_FILE_DEBUG=./develop/docker-compose-debug.yml BUILD_NAME=netbox_qrcode VERFILE=./netbox_qrcode/version.py - +# Build Docker with the specific Python and Netbox version cbuild: docker-compose -f ${COMPOSE_FILE} \ -p ${BUILD_NAME} build \ --build-arg netbox_ver=${NETBOX_VER} \ --build-arg python_ver=${PYTHON_VER} -debug: +# Start Docker with terminal window output +debug: cbuild @echo "Starting Netbox .. " docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} up +# Start Docker with terminal window output. +# Brakepoints in e.g. Python files are supported in VSCode. Changes in Python and HTML are applied after saving. +debug-vscode: cbuild + @echo "Starting Netbox debug for VSCode.. " + docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} -f ${COMPOSE_FILE_DEBUG} up +# --build + +# Start Docker without connecting to the terminal window. (Runs independently of the terminal window.). start: @echo "Starting Netbox in detached mode.. " docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} up -d +# Stop Docker Container stop: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} down +# Stop Docker and remove containers destroy: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} down docker volume rm -f ${BUILD_NAME}_pgdata_netbox_qrcode +# Calls the Netbox shell. Exit with exit() +# NetBox includes a Python shell within which objects can be directly queried, created, modified, and deleted. nbshell: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} run netbox python manage.py nbshell +# Calls the Python shell. Exit with exit() shell: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} run netbox python manage.py shell +# To create the Django Superuser to be able to log on to Netbox Web. adduser: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} run netbox python manage.py createsuperuser +# Django collectstatic collectstatic: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} run netbox python manage.py collectstatic +# Migrate the Netbox system depending on the Django model migrations: docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} up -d postgres docker-compose -f ${COMPOSE_FILE} -p ${BUILD_NAME} \ diff --git a/README.md b/README.md index 2b2f6dc..98e263c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Netbox QR Code Plugin -[Netbox](https://github.com/netbox-community/netbox) plugin for generate QR codes for objects: Rack, Device, Cable. +[Netbox](https://github.com/netbox-community/netbox) plugin for generate QR codes for objects: Device, Module, Cable, Powerfeed, Powerpanel, Location This plugin depends on [qrcode](https://github.com/lincolnloop/python-qrcode) and [Pillow](https://github.com/python-pillow/Pillow) python library @@ -10,10 +10,14 @@ This plugin depends on [qrcode](https://github.com/lincolnloop/python-qrcode) an ## Compatibility -| Plugin Version | NetBox Version | Tested on | -| ------------- |:-------------| :-----:| -| 0.0.11 | >= 3.7.0 | 3.7.x | -| 0.0.13 | >= 4.0.2 | v4.0.2 | +| Plugin Version | NetBox Version | Tested on | +| ------------- |:-------------| :-----------:| +| 0.0.11 | 3.7.x | 3.7.x | +| 0.0.14 | 4.0.x | 4.0.11 | +| 0.0.15 | 4.1.x | 4.1.6 | +| 0.0.17 | 4.2.x | 4.2.4 | +| 0.0.18 | 4.3.x | 4.3.1 | + ## Installation @@ -36,80 +40,72 @@ Restart NetBox and add `netbox-qrcode` to your local_requirements.txt ## Configuration -The following options are available: +### Label Design -* `with_text`: Boolean (default True). Text label will be added to QR code image if enabled. -* `text_template`: [Jinja2](https://jinja.palletsprojects.com/) template with {{ obj }} as context for device, rack, etc., using it ignores `text_fields` and `custom_text`. +Extensive label customisation is possible, it's also possible to include different labels for each object type, for example 2 labels for the Device view. - Example to output name and site in two lines with caption (See also screenshots below): - ``` - 'text_template': 'Name: {{ obj.name }}\nSite: {{ obj.site }}', - ``` -* `text_fields`: List of String (default ['name']). Text fields of an object that will be added as text label to QR image. It's possible to use custom field values. -* `font`: String (default TahomaBold) Font name for text label ( Some font include in package, see fonts dir). -* `text_location`: Where to render the text, relative to the QR code. Valid values are `"right"` (default), `"left"`", `"up"`, and `"down"`. -* `custom_text`: String or None (default None) additional text label to QR code image (will be added after text_fields). -* `qr_version`: Integer (default 1) parameter is an integer from 1 to 40 that controls the size of -the QR Code (the smallest, version 1, is a 21x21 matrix). -* `qr_error_correction`: Integer (default 0), controls the error correction used for the -QR Code. The following values are available: +For advice on configuration please see the two links below: - 1 - About 7% or less errors can be corrected. - 0 - About 15% or less errors can be corrected. - 2 - About 30% or less errors can be corrected. - 3 - About 25% or less errors can be corrected. +- [General Configuration >>](docs/README_Subpages/README_Configuration.md) +- [Configuration Examples >>](docs/README_Subpages/README_Configuration_ExampleLabelConf.md) -* `qr_box_size`: Integer (default 6), controls how many pixels each "box" of the QR code -is. -* `qr_border`: Integer (default 4), controls how many boxes thick the border should be -(the default is 4, which is the minimum according to the specs). +![Cable QR Code](/docs/img/Configuration_Label_Example_10.png) -### Per object options -Per object options override default options. Per object options dictionary can contains any of default options inside. +### Printing + +#### Setting the label printer + +If the print does not look like the preview in the Netbox, first try to get a perfect print using Word. As many printer settings also have an influence on the print result. Borderless printing is possible if the printer (e.g. thermal transfer printer) supports this. + +![ShowImage](/docs/img/Configuration_Printer_WordPreview.png) + + +Here is an example of what needs to be considered to print borderless from a Word document. [Go to: Example Zebra ZM400 300dpi label printer and a label 56x32mm. >>](/docs/img/Configuration_Printer_ZM400.png) + + +#### Setting Browser Print Settings + +When you press the “Print” button, there are some print properties that are added by the browser. However, these interfere with the print result. They should therefore be deactivated. + +##### Firefox: + +| Parameter | Value | +| --------------------------------------------- | --------------------------- | +| Orientation | Portrait | +| Paper size | User defined | +| Margins | none | +| Scale | Fit to page width or 100% | +| Options --> Print headers and footers | disable | +| Options --> Print backgrounds | disable | + +##### Chrome: +Chrome can alter settings between printing and the print preview, therefore the below settings are recomended + +| Parameter | Value | +| --------------------------------------------- | --------------------------- | +| Layout | Portrait | +| Paper size | empty !!! | +| Pages per sheet | 1 | +| Margins | none | +| Scale | Default or 100% | +| Options --> PBackground grafics | disable | + +![Image](/docs/img/Configuration_Browser_Print_Settings.png) + -* `device`: Dict or None (default {'text_fields': ['name', 'serial']}), set None to disble QR code -* `rack`: Dict or None (default {'text_fields': ['name']}) , set None to disble QR code -* `cable`: Dict or None ( defaul {'text_fields': ['_termination_a_device', 'termination_a', '_termination_b_device', 'termination_b',]}), set None to disble QR code -Configuration example: -``` -PLUGINS_CONFIG = { - 'netbox_qrcode': { - 'with_text': True, - 'text_fields': ['name', 'serial'], - 'font': 'ArialMT', - 'font_size': 12, # If the value is 0 or the line does not exist, then the text is automatically adjusted - 'custom_text': 'Property of SomeCompany\ntel.8.800333554-CALL', - 'text_location': 'up', - 'qr_version': 1, - 'qr_error_correction': 0, - 'qr_box_size': 4, - 'qr_border': 4, - # per object options - 'cable': None, # disable QR code for Cable object - 'rack': { - 'text_fields': [ - 'site', - 'name', - 'facility_id', - 'tenant', - 'cf.cf_name' - ] - }, - 'device': { - 'qr_box_size': 6, - 'custom_text': None, - } - } -} -``` ## Contributing -Developing tools for this project based on [ntc-netbox-plugin-onboarding](https://github.com/networktocode/ntc-netbox-plugin-onboarding) repo. Issues and pull requests are welcomed. +## Development + +You would like to help with the development of the plugin. Here you can find further information such as how to set up debugging in vscode and much more. + +- [Go to Development >>](docs/README_Subpages/README_Development.md) + ## Screenshots Device QR code with text label @@ -120,6 +116,3 @@ Rack QR code Cable QR code ![Cable QR Code](docs/img/qrcode_cable.png) - -Device QR code via Jinja2 "text_template" Parameter (Multiline and labeled) -![Cable QR Code](docs/img/qrcode_text_template.png) \ No newline at end of file diff --git a/develop/Dockerfile b/develop/Dockerfile index a2c56d2..49a950a 100644 --- a/develop/Dockerfile +++ b/develop/Dockerfile @@ -1,12 +1,14 @@ ARG python_ver=3.12 FROM python:${python_ver} -ARG netbox_ver=master +ARG netbox_ver=main ENV PYTHONUNBUFFERED 1 RUN mkdir -p /opt RUN pip install --upgrade pip +RUN pip install setuptools + # ------------------------------------------------------------------------------------- # Install NetBox @@ -23,6 +25,6 @@ WORKDIR /source COPY . /source #RUN pip install -r requirements.txt -RUN python setup.py develop +RUN python -m pip install -e . WORKDIR /opt/netbox/netbox/ diff --git a/develop/Netbox_Icon.png b/develop/Netbox_Icon.png new file mode 100644 index 0000000..f77561b Binary files /dev/null and b/develop/Netbox_Icon.png differ diff --git a/develop/configuration.py b/develop/configuration.py index b564c3f..fdc1217 100644 --- a/develop/configuration.py +++ b/develop/configuration.py @@ -157,7 +157,225 @@ # Plugins configuration settings. These settings are used by various plugins that the user may have installed. # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. -# PLUGINS_CONFIG = {} +PLUGINS_CONFIG = { + + 'netbox_qrcode': { + + ## Example Template for Devices + + 'device_2': { + 'title': 'Example 2 (Template for Device)', + 'text_template': '

{{ obj.name }}
Device: {{ obj.id|stringformat:"07d" }}', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '30mm', + 'label_qr_text_distance': '2mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '2mm', + 'label_edge_right': '2mm', + 'logo': '/media/image-attachments/Netbox_Icon_Example.png', + }, + + 'device_3': { + 'title': 'Example 3 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '15mm', + 'label_qr_height': '15mm', + 'label_qr_text_distance': '2mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '2mm', + 'label_edge_right': '2mm', + 'text_location': 'left', + }, + + 'device_4': { + 'title': 'Example 4 (Template for Device)', + 'font_size': '4mm', + 'text_align_horizontal': 'center', + 'text_align_vertical': 'middle', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'with_text': True, + 'with_qr': False, + }, + + 'device_5': { + 'title': 'Example 5 (Template for Device)', + 'font_size': '4mm', + 'text_align_horizontal': 'left', + 'text_align_vertical': 'middle', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '2mm', + 'label_edge_right': '0mm', + 'with_text': True, + 'with_qr': False, + }, + + 'device_6': { + 'title': 'Example 6 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'with_text': False, + 'with_qr': True, + }, + + 'device_7': { + 'title': 'Example 7 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '32mm', + 'label_height': '56mm', + 'label_edge_top': '2mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'text_location': 'up', + 'text_align_horizontal': 'center', + 'text_align_vertical': 'top', + 'label_edge_bottom': '2mm', + }, + + 'device_8': { + 'title': 'Example 8 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '2mm', + 'label_width': '32mm', + 'label_height': '56mm', + 'label_edge_top': '2mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'text_location': 'down', + 'text_align_horizontal': 'center', + 'text_align_vertical': 'top', + }, + + 'device_9': { + 'title': 'Example 9 (Template for Device)', + 'font_size': '1mm', + 'label_qr_width': '9mm', + 'label_qr_height': '9mm', + 'label_qr_text_distance': '1mm', + 'label_width': '25mm', + 'label_height': '10mm', + 'label_edge_top': '0.2mm', + 'label_edge_left': '0.2mm', + 'label_edge_right': '0mm', + }, + + 'device_10': { + 'title': 'Example 10 (Template for Device)', + 'with_qr': False, + 'text_align_horizontal': 'center', + 'text_align_vertical': 'middle', + 'text_template': '

' + '{{ obj.name }}
' + '

' + 'Device: {{ obj.id|stringformat:"07d" }}' + '

😝 My label design 😝

', + + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + }, + + + ## Example Template for Cables + + 'cable': { + 'title': 'Example 1 (Template for Cable)', + 'with_qr': False, + 'label_edge_left': '0.00mm', + 'label_edge_right': '0.00mm', + 'label_edge_top': '0.00mm', + 'text_align_vertical': 'middle', + 'text_align_horizontal': 'center', + 'text_template': '' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '
' + }, + + 'cable_2': { + 'title': 'Example 2 (Template for Cable)', + 'with_qr': False, + 'label_edge_left': '0.00mm', + 'label_edge_right': '0.00mm', + 'label_edge_top': '0.00mm', + 'text_align_vertical': 'middle', + 'text_align_horizontal': 'center', + + # QR-Code Image File + 'qr_version': 1, + 'qr_error_correction': 1, + 'qr_box_size': 2, + 'qr_border': 0, + + 'text_template': '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + } + } +} # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to # prefer IPv4 instead. diff --git a/develop/docker-compose-debug.yml b/develop/docker-compose-debug.yml new file mode 100644 index 0000000..eb5357a --- /dev/null +++ b/develop/docker-compose-debug.yml @@ -0,0 +1,34 @@ +# This Docker Compose Debug File supports you with debugging in VSCode For more details see README.md + +# Overwrites parts of the service from the Docker-Compose file: +--- +services: + netbox: + # The following commands are executed + # - Install Current Version of DebugPy for Debug in Visual Studio Code https://github.com/microsoft/debugpy + # - PYDEVD_DISABLE_FILE_VALIDATION=1 --> You’re telling the debugger to skip this validation and proceed with debugging + # - Migrate Netbox + # - Create a user (superuser) for Netbox / Django so that you can log in directly to Netbox. More Infos: makesuperuser.py + # - Start Netbox with Modul DebugPy (Xfrozen_modules=off --> Disable Frozen Moduls in DebugPy Mode; 5678 is Debug Port) + command: > + sh -c "echo Run Netbox Django in debug mode && + pip install debugpy && + PYDEVD_DISABLE_FILE_VALIDATION=1 && + python manage.py migrate && + python manage.py makesuperuser && + python -Xfrozen_modules=off -m debugpy --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8000 --insecure" + ports: + - '8000:8000' + - '5678:5678' + worker: + # Enables the synchronization of source code files with the Docker container during debugging. + # Without this option, changed files in the Docker container are only transferred to VSCode. + # Currently HTML files are changed live. After saving a *.py file, the container is automatically rebuilt. I don't know if this can be optimized. Greetings LHBL2003. + # https://www.docker.com/blog/docker-compose-experiment-sync-files-and-automatically-rebuild-services-with-watch-mode/ + x-develop: + watch: + - action: sync + path: ../netbox_qrcode + target: /source/netbox_qrcode + #- action: rebuild + #path: package.json diff --git a/develop/docker-compose.yml b/develop/docker-compose.yml index 4b7e984..d80ef99 100644 --- a/develop/docker-compose.yml +++ b/develop/docker-compose.yml @@ -5,8 +5,14 @@ services: build: context: ../ dockerfile: develop/Dockerfile + + # The following commands are executed + # - Migrate Netbox + # - Create a user (superuser) for Netbox / Django so that you can log in directly to Netbox. More Infos: makesuperuser.py + # - Start Netbox command: > sh -c "python manage.py migrate && + python manage.py makesuperuser && python manage.py runserver 0.0.0.0:8000" ports: - '8000:8000' @@ -17,7 +23,10 @@ services: - ./dev.env volumes: - ./configuration.py:/opt/netbox/netbox/netbox/configuration.py + - ../docs/img/Netbox_Icon_Example.png:/opt/netbox/netbox/media/image-attachments/Netbox_Icon_Example.png - ../netbox_qrcode:/source/netbox_qrcode + # For automatic setup of the Django / Netbox SuperUser + - ../develop/management:/source/netbox_qrcode/management tty: true worker: build: @@ -33,7 +42,7 @@ services: - ../netbox_qrcode:/source/netbox_qrcode tty: true postgres: - image: postgres:12 + image: postgres:16 env_file: dev.env volumes: - pgdata_netbox_qrcode:/var/lib/postgresql/data diff --git a/develop/management/__init__.py b/develop/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/develop/management/commands/__init__.py b/develop/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/develop/management/commands/makesuperuser.py b/develop/management/commands/makesuperuser.py new file mode 100644 index 0000000..7147ca0 --- /dev/null +++ b/develop/management/commands/makesuperuser.py @@ -0,0 +1,37 @@ +# This script creates the Django Super User which is also used for the first login to Netbox. +# This means that no user needs to be created in the development phase, as this is done when Docker-Compose is started. +# The script is terminated prematurely if a user already exists. +# User name and password see parameters below "Username" and "password" +# +# This script must be located in the Django plugin path .../management/comands at development time. +# e.g.: Docker-Compose volumes: ../develop/management:/source/netbox_qrcode/management +# e.g.: Docker-Compose command: "python manage.py makesuperuser". + +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + +User = get_user_model() + +class bcolors: + PURPLE = '\033[95m' + RED = '\033[91m' + +class Command(BaseCommand): + def handle(self, *args, **options): + username = 'admin' + email = 'admin@admin.local' + password = 'admin' + try: + u = None + if not User.objects.filter(username=username).exists() and not User.objects.filter(is_superuser=True).exists(): + print("admin user not found, creating one") + + u = User.objects.create_superuser(username, email, password) + print(f"{bcolors.PURPLE}===================================") + print(f"{bcolors.PURPLE}A superuser for NetBox / Django '{username}' was created with email '{email}' and password '{password}'") + print(f"{bcolors.PURPLE}===================================") + else: + print(f"{bcolors.PURPLE}A superuser for NetBox / Django Admin user already exists or was created during the first Docker-Compose start.") + print(u) + except Exception as e: + print(f"{bcolors.RED}There was an error: {e}") \ No newline at end of file diff --git a/docs/README_Subpages/README_Configuration.md b/docs/README_Subpages/README_Configuration.md new file mode 100644 index 0000000..e9b9c41 --- /dev/null +++ b/docs/README_Subpages/README_Configuration.md @@ -0,0 +1,662 @@ +[<< Back to README start](/README.md) + +# Configuration +The plugin is configured in the Netbox configuration file: configuration.py +If you have followed the standard installation of Netbox, the configuration file is located under: /opt/netbox/netbox/netbox/configuration.py + +The following options are available. If the same variable name is specified several times in several lines, these are different examples. Entries with ‘# DEFAULT’ are system defaults. + +## General Plugin + +* `title`: + + A title is displayed in the label window. This can be expanded with information. Useful if you want to provide several label variants for devices, for example. + ```Python + 'title': '', # DEFAULT + 'title': 'My text extension in the plugin heading.', + ``` + +## Text content + +* `with_text`: + + Text label will be added to QR code image if enabled. + + ```Python + 'with_text': True, # DEFAULT + 'with_text': False, + ``` + +* `text_location`: + + Where to render the text, relative to the QR code. + + ```Python + 'text_location': 'right', # DEFAULT + 'text_location': 'left', + 'text_location': 'up', + 'text_location': 'down', + ``` + +* `text_align_horizontal`: + + Defines where the text is to be displayed horizontally in the text area. + + ```Python + 'text_align_horizontal': 'left', # DEFAULT + 'text_align_horizontal': 'center', + 'text_align_horizontal': 'right', + ``` + +* `text_align_vertical`: + + Defines where the text is to be displayed vertically in the text area. + + ```Python + 'text_align_horizontal': 'middle', # DEFAULT + 'text_align_horizontal': 'top', + 'text_align_horizontal': 'bottom', + ``` + + ### Text source (Option A) + +* `text_fields`: + + Text fields of an object that will be added as text label to QR image. It's possible to use custom field values. + + ```Python + 'text_fields': ['name', 'serial'], # DEFAULT + 'text_fields': ['name'], + 'text_fields': ['site', + 'name', + 'id'] + ``` + +* `custom_text`: Additional text label to QR code image (will be added after text_fields). + ```Python + 'custom_text': None, # DEFAULT + 'custom_text': 'My Text', + ``` + ### Text source (Option B) + +* `text_template`: + + [Jinja2](https://jinja.palletsprojects.com/) template with {{ obj }} as context for device, rack, etc., using it ignores `text_fields` and `custom_text`. + + **{{ obj.xyz }} :** + + Value of the object/module (e.g. device, rack, etc.). Which values can be read depends on the Netbox module. Many names can be found during mass import, e.g. https://server/dcim/devices/import/, i.e. the “Field Options” names such as “status”, i.e. “obj.status”. Writing HTML text is permitted. + + ```Python + # Example to output name and site in two lines with caption (See also screenshots below): + + 'text_template': 'Name: {{ obj.name }}\nSite: {{ obj.site }}', + ``` + + ![Cable QR Code](/docs/img/qrcode_text_template.png) + + **{{ logo }}** : + + The value stored in the logo parameter. + + ```Python +
``` + ``` + + **{{ qrCode }}** : + + The QR code is provided as a Base64 string. + + ```Python +
+ ``` + + + +## Font + +* `font`: + + Font name for text label (All fonts supported by the browser are supported. [Web-Safe-Fonts](https://www.w3schools.com/cssref/css_websafe_fonts.php) are recommended as these are supported on all common systems without the need for subsequent installation. + + ```Python + 'font': 'TahomaBold' # DEFAULT + 'font': 'Arial', + 'font': 'Verdana', + 'font': 'Arial, Verdana', # If Arial is not available then Verdana. + 'font': '\'Trebuchet MS\'', # If blank character in font name. + ``` + +* `font_size`: + + Defines the height of the font. + + ```Python + 'font_size': '3.00mm', # DEFAULT + 'font_size': '0.11in', # For inches + ``` + +* `font_weight`: + + Determines how dense the font is. + + ```Python + 'font_weight': 'normal', # DEFAULT + 'font_weight': 'bold', + 'font_weight': 'lighter', + 'font_weight': 'bolder', + ``` + +* `font_color`: + + Determines how dense the font is. (More Color Infos https://www.w3schools.com/html/html_colors.asp) + + ```Python + 'font_color': 'black', # DEFAULT + 'font_color': 'red', + 'font_color': 'rgb(255, 0, 0)', # blue + 'font_color': '#6a5acd', # violett + ``` + +## QR-Code + +* `with_qr`: + + QR-Code label will be added to label if enabled. + + ```Python + 'with_qr': True, # DEFAULT + 'with_qr': False, + ``` + + ### QR-Code alternative source + + By default, the URL path to the object is used as the text for the QR code. Below is an alternative for defining your own QR code content. + +* `url_template`: [Jinja2](https://jinja.palletsprojects.com/) template with {{ obj }} as context for device, rack, etc. + + ```Python + # Example to output the object name and the ID in the QR code. + (This means that information other than the Netbox URL can also be output in the QR code): + + 'url_template': None, # DEFAULT + 'url_template': '{{ obj.name }} - Objekt ID: {{ obj.id }}', + ``` + + ### QR-Code Image File + These parameters are used to create the QR code image file. + +* `qr_version`: + + For the image file: Parameter is an integer from 1 to 40 that controls the size of the QR Code (the smallest, version 1, is a 21x21 matrix). More Information see: [qrcode 3.0 Parameter "version"](https://pypi.org/project/qrcode/3.0/) + + Experience: The higher the number, the more often it appears as if the QR codes are repeated. The value 1 is recommended for beginners. + + ```Python + 'qr_version': 1, # DEFAULT + ``` + +* `qr_error_correction`: + + For the image file: This value is used to create the QR code image file. Controls the error correction used for the QR Code. More Information see: [qrcode 3.0 Parameter "error_correction"](https://pypi.org/project/qrcode/3.0/) + + Experience: You can scratch x% off the QR code before it is no longer readable. + + ```Python + 'qr_error_correction': 0, # DEFAULT - About 15% or less errors can be corrected. + 'qr_error_correction': 1, # About 7% or less errors can be corrected. + 'qr_error_correction': 2, # About 30% or less errors can be corrected. + 'qr_error_correction': 3, # About 25% or less errors can be corrected. + ``` + +* `qr_box_size`: + + For the image file: This value is used to create the QR code image file. Controls how many pixels each "Black boxes within the QR code" of the QR code is. More Information see: [qrcode 3.0 Parameter "box_size"](https://pypi.org/project/qrcode/3.0/) + + Experience: The larger the value, the larger the QR code image file will be. It also takes longer to create the image file. If a value that is too small is used, the QR code may become unscaled if label_qr_width and label_qr_height have values that are too large. + + ```Python + 'qr_box_size': 4, # DEFAULT + ``` + +* `qr_border`: + + For the image file: This value is used to create the QR code image file. Defines the border width when rendering the QR code image file. More Information see: [qrcode 3.0 Parameter "qr_border"](https://pypi.org/project/qrcode/3.0/) + + Experience: Value 0, as the size of the QR code can be better influenced by 'label_qr_width', label_qr_height and the centered positioning creates a margin. The size of the QR code can therefore be better adjusted. However, there should be a border around the QR code on the label. + + ```Python + 'qr_border': 0, # DEFAULT. + ``` + +* `Summery qr_... `: + + This table should show a few combinations of qr_[parameter name] and their resulting QR code image file sizes. With the values in column 4 you can see that you already get a 4cm x 4cm QR code image file. + + | | Test | Test | Test | Test |Test | Test |Test |Test |Test | Test | + | ------------------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | + | qr_version | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 4 | 6 | 40 | + | qr_box_size | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 6 | 6 | 1 | + | qr_error_correction | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | + | qr_border | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | + | DPI | 72 | 72 | 72 | 72 | 72 | 72 | 72 | 72 | 72 | 72 | + | cm (H/W) | `1,02` | `2,05` | `3,07` | `4,09` | `5,12` | `6,14` | `6,14` | `6,99` | `8,68` | `6,24` | + | Pixel (H/W) | `29` | `58` | `87` | `116` | `145` | `174` | `174` | `198` | `246` | `177` | + +## Label Layout +The parameters that can be used to design the label are listed below. + +### Label dimensions +* `label_width`: + + Indicates how wide the physical label is. + + ```Python + 'label_width': '56mm', # DEFAULT + 'label_width': '2.20in', # For inch + ``` + +* `label_height`: + + Indicates how high the physical label is. + + ```Python + 'label_height': '32mm', # DEFAULT + 'label_height': '1.26in', # For inch + ``` + +### Label edge +* `label_edge_top`: + + Indicates how high the physical label is. + + ```Python + 'label_edge_top': '0mm', # DEFAULT + 'label_edge_top': '0in', # For inch + ``` + +* `label_edge_left`: + + Indicates how high the physical label is. + + ```Python + 'label_edge_left': '1.5mm', # DEFAULT + 'label_edge_left': '0.59in', # For inch + ``` + +* `label_edge_right`: + + Indicates how high the physical label is. + + ```Python + 'label_edge_right': '1.5mm', # DEFAULT + 'label_edge_right': '0.59in', # For inch + ``` + +* `label_edge_bottom`: + + Indicates how high the physical label is. + + ```Python + 'label_edge_bottom': '0mm', # DEFAULT + 'label_edge_bottom': '0in', # For inch + ``` + +### Label QR code positioning +* `label_qr_width`: + + Specifies how wide the QR code image should be displayed on the label. + + ```Python + 'label_qr_width': '12mm', # DEFAULT + 'label_qr_width': '0.47in', # For inch + ``` + +* `label_qr_height`: + + Specifies how high the QR code image should be displayed on the label. + + ```Python + 'label_qr_height': '12mm', # DEFAULT + 'label_qr_height': '0.47in', # For inch + ``` + +* `label_qr_text_distance`: + + Specifies how large the distance between the QR code and the text should be. + + ```Python + 'label_qr_text_distance': '1mm', # DEFAULT + 'label_qr_text_distance': '0.039in', # For inch + ``` + +## LOGO / Image + +* `logo`: + + Enables a logo/image to be inserted in combination with the `text_template` parameter. + + ### Image File size: + + To keep the traffic and performance high, the image should be as small as necessary. An image with 1920x1200px and 30MB is nonsensical if it is to be 1.00x3.00cm in size at the end. The larger the image, the longer the Base64 text. If possible, the logo is completely black and not gray or colored, which makes it even smaller. (Sufficient for thermal transfer printers). + + Here are a few helpful online tools: + Resize image online (in mm): https://image.pi7.org/resize-image-in-mm + Image compression (e.g. 100kb to 2kb): https://tinypng.com/ + + ### Version 1: Link to the image (Simpler) + + This variant shows how you can link a logo. + + ```Python + 'logo': '/media/image-attachments/Netbox_Icon_Example.png', + ``` + + Below is how you can embed the logo in a text. + It is an HTML block that contains the image {{ logo }} as well as the name {{ obj.name }} and the ID of the object {{ obj.id }} in 3 lines. + + ```Python + 'text_template': '

{{ obj.name }}
Device: {{ obj.id }}
', + ``` + + ℹ️ The image links used in the examples are merely placeholders and are not predefined by the plugin. You can therefore freely adapt them to suit your environment. Links with http:// or https:// should also work as long as the client can access them. The NetBox media link used in the example is only one possible variant that has been successfully tested in a classic NetBox installation. As a guide, the media path used in the example is usually located in the following directory: /opt/netbox/netbox/media/ + + ℹ️ The logo link can be inserted directly in the text_template, but this is not recommended as the logo parameter may be included in the standard layout in future versions. + + ### Version 2: Embedded Base64 Image (Complex) + + Convert image to Base64 string.: https://www.base64-image.de/ + + Your image should have the following text format at the end. + ```html + data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABysAAAIDCA... + ``` + + Below is the Logo parameter for a Netbox logo. (Image specification: black/white,Background Transparent , 3,71KB, 202px x 57px, 71.26mm x 20.00mm) + + It is recommended to place the Logo entry at the end of the plugin configuration, as the Base64 string can be quite long. It is also recommended to place the PLUGINS_CONFIG = {} block in Configuration.py at the end of the file. + + ```Python + 'logo': 'data:image/png;base64,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', + ``` + + Below is how you can embed the logo in a text. + It is an HTML block that contains the image {{ logo }} as well as the name {{ obj.name }} and the ID of the object {{ obj.id }} in 3 lines. + + ```Python + 'text_template': '

{{ obj.name }}
Device: {{ obj.id }}
', + ``` + + If you pay attention to the proportional ratio, you can also adjust the size of the image. e.g. 5.00mm x 17.80mm. The size can also be specified in inches (in). + ```Python + 'text_template': '

{{ obj.name }}
Device: {{ obj.id }}
', + ``` + +## Global Configuration +The following shows an example configuration of how to adjust parameters for all objects/modules (e.g. device, rack, etc.) at once. However, if there is a separate configuration for the device, for example, this has priority. + +```Python +PLUGINS_CONFIG = { + 'netbox_qrcode': { + ################################## + # General Plugin + 'title': 'My Text in headline', + 'font_size': '5.12mm', # DEFAULT + } +} +``` + +## Module-dependent configuration +It is possible, for example, to overwrite the default values or the global settings from above for an (object/module) class such as Device. One or more parameters can also be overwritten here. + +Example: + +```Python +PLUGINS_CONFIG = { + 'netbox_qrcode': { + ################################## + # General Plugin + 'title': 'My text for all headlines', + 'font_size': '5.12mm', + } + 'device': { + 'title': 'My text for the headline Device', + 'font_size': '10mm', + }, +} +``` + +**Below are the default settings of the plugin.** + +* `device`: + + Specific label customization for the calculated https://server/dcim/devices/ + + ```Python + 'device': { + 'text_fields': ['name', 'serial'] # DEFAULT + }, + ``` + +* `rack`: + + Specific label customization for the calculated https://server/dcim/racks/ + + ```Python + 'rack': { + 'text_fields': ['name'] # DEFAULT + }, + ``` + +* `cable`: + + Specific label customization for the calculated https://server/dcim/cables/ + + ```Python + 'cable': { + 'text_fields': [ + '_termination_a_device', + 'termination_a', + '_termination_b_device', + 'termination_b', + 'a_terminations.device', + 'a_terminations', + 'b_terminations.device', + 'b_terminations' + ] # DEFAULT + }, + ``` + +* `location`: + + Specific label customization for the calculated https://server/dcim/locations/ + + ```Python + 'location': { + 'text_fields': ['name'] # DEFAULT + }, + ``` + +* `powerfeed`: + + Specific label customization for the calculated https://server/dcim/locations/ + + ```Python + 'powerfeed': { + 'text_fields': ['name'], # DEFAULT + }, + ``` +* `powerpanel`: + + Specific label customization for the calculated https://server/dcim/locations/ + + ```Python + 'powerpanel': { + 'text_fields': ['name'], # DEFAULT + }, + ``` + +* `modul_X`: + + It is possible to store several label layouts for an object/module (e.g. device, rack, etc.). For example, for other label information or other label sizes. To do this, an increment starting with 2 to n must be specified at the end. No numbers may be skipped in the sequence. Example: device; device_2; device_3; device_5. “device_5” is not reached because 4 is missing. + + ```Python + 'device_2': { + 'title': 'My Litle Label', + + 'label_width': '25mm', + 'label_height': '10mm', + + 'font_size': '2mm', + 'font_weight': 'bold', + + 'label_qr_width': '7.00mm', + 'label_qr_height': '9.00mm', + 'label_qr_text_distance': '0.4mm', + + 'label_edge_left': '0.50mm', + 'label_edge_right': '0.00mm', + + 'text_fields': ['name'], + }, + ``` + ```Python + 'device_3': { + ... + }, + ``` + ```Python + 'rack_2': { + ... + }, + ``` + +# Example configuration: + +This is a sample template of what a configuration can look like. But as I said, you do not have to specify all parameters. + +```Python +PLUGINS_CONFIG = { + 'netbox_qrcode': { + ################################## + # General Plugin + 'title': '', + + ################################## + # Text content + 'with_text': True, + 'text_location': 'right', + 'text_align_horizontal': 'left', + 'text_align_vertical': 'middle', + + # Text source (Option A) + 'text_fields': ['name', 'serial'], + 'custom_text': None, + + # Text source (Option B) + 'text_template': None, + + ################################## + # Font + 'font': 'TahomaBold', + 'font_size': '3mm', + 'font_weight': 'normal', + 'font_color': 'black', + + ################################## + # QR-Code + 'with_qr': True, + + # QR-Code alternative source + 'url_template': None, + + # QR-Code Image File + 'qr_version': 1, + 'qr_error_correction': 0, + 'qr_box_size': 4, + 'qr_border': 0, + + ################################## + # Label Layout + + # Label dimensions + 'label_qr_width': '12mm', + 'label_qr_height': '12mm', + + # Label edge + 'label_edge_top': '0mm', + 'label_edge_left': '1.5mm', + 'label_edge_right': '1.5mm', + 'label_edge_bottom': '0mm', + + # Label QR code positioning + 'label_width': '56mm', + 'label_height': '32mm', + 'label_qr_text_distance': '1mm', + + ################################## + # Module-dependent configuration + + 'device': { + 'text_fields': ['name', 'serial'] + }, + + 'device_2': { + 'title': 'My Litle Label', + + 'label_width': '25mm', + 'label_height': '10mm', + + 'font_size': '2mm', + 'font_weight': 'bold', + + 'label_qr_width': '7.00mm', + 'label_qr_height': '9.00mm', + 'label_qr_text_distance': '0.4mm', + + 'label_edge_left': '0.50mm', + 'label_edge_right': '0.00mm', + + 'text_fields': ['name'], + }, + + 'rack': { + 'text_fields': ['name'] + }, + + 'cable': { + 'text_fields': [ + '_termination_a_device', + 'termination_a', + '_termination_b_device', + 'termination_b', + 'a_terminations.device', + 'a_terminations', + 'b_terminations.device', + 'b_terminations' + ] + }, + + 'location': { + 'text_fields': ['name'] + }, + + 'powerfeed': { + 'text_fields': ['name'] + }, + + 'powerpanel': { + 'text_fields': ['name'] + }, + + 'logo': '' + } +} +``` + +## Example label configurations +[Go to Example label configurations >>](README_Configuration_ExampleLabelConf.md) + +![Cable QR Code](/docs/img/Configuration_Label_Example_10.png) + +[<< Back to README start](/README.md) \ No newline at end of file diff --git a/docs/README_Subpages/README_Configuration_ExampleLabelConf.md b/docs/README_Subpages/README_Configuration_ExampleLabelConf.md new file mode 100644 index 0000000..66d7866 --- /dev/null +++ b/docs/README_Subpages/README_Configuration_ExampleLabelConf.md @@ -0,0 +1,304 @@ +[<< Back to General Configuration](README_Configuration.md) + +# Example label configurations + +## Example 1 (Device) - Default +Standard label without customization + +![Cable QR Code](/docs/img/Configuration_Label_Example_01.png) + +## Example 2 (Device) +QR code Higher than wide, with own logo Device name and Device ID filled with zeros. + +![Cable QR Code](/docs/img/Configuration_Label_Example_02.png) + +```Python + 'device_2': { + 'title': 'Example 2 (Template for Device)', + 'text_template': '

{{ obj.name }}
Device: {{ obj.id|stringformat:"07d" }}', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '30mm', + 'label_qr_text_distance': '2mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '2mm', + 'label_edge_right': '2mm', + 'logo': '/media/image-attachments/Netbox_Icon_Example.png', + }, +``` +## Example 3 (Device) +Text left QR code right with distance to the edge. + +![Cable QR Code](/docs/img/Configuration_Label_Example_03.png) + +```Python + 'device_2': { + 'title': 'Example 3 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '15mm', + 'label_qr_height': '15mm', + 'label_qr_text_distance': '2mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '2mm', + 'label_edge_right': '2mm', + 'text_location': 'left', + }, +``` + +## Example 4 (Device) +Center text only + +![Cable QR Code](/docs/img/Configuration_Label_Example_04.png) + +```Python + 'device_2': { + 'title': 'Example 4 (Template for Device)', + 'font_size': '4mm', + 'text_align_horizontal': 'center', + 'text_align_vertical': 'middle', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'with_text': True, + 'with_qr': False, + }, +``` + +## Example 5 (Device) +Text links only + +![Cable QR Code](/docs/img/Configuration_Label_Example_05.png) + +```Python + 'device_2': { + 'title': 'Example 5 (Template for Device)', + 'font_size': '4mm', + 'text_align_horizontal': 'left', + 'text_align_vertical': 'middle', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '2mm', + 'label_edge_right': '0mm', + 'with_text': True, + 'with_qr': False, + }, +``` + +## Example 6 (Device) +QR code only + +![Cable QR Code](/docs/img/Configuration_Label_Example_06.png) + +```Python + 'device_2': { + 'title': 'Example 6 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'with_text': False, + 'with_qr': True, + }, +``` + +## Example 7 (Device) +Upright, large font with line break and serial number + +![Cable QR Code](/docs/img/Configuration_Label_Example_07.png) + +```Python + 'device_7': { + 'title': 'Example 7 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '0mm', + 'label_width': '32mm', + 'label_height': '56mm', + 'label_edge_top': '2mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'text_location': 'up', + 'text_align_horizontal': 'center', + 'text_align_vertical': 'top', + 'label_edge_bottom': '2mm', + }, +``` + +## Example 8 (Device) +Upright, large font with line break and serial number + +![Cable QR Code](/docs/img/Configuration_Label_Example_08.png) + +```Python + 'device_8': { + 'title': 'Example 8 (Template for Device)', + 'font_size': '4mm', + 'label_qr_width': '20mm', + 'label_qr_height': '20mm', + 'label_qr_text_distance': '2mm', + 'label_width': '32mm', + 'label_height': '56mm', + 'label_edge_top': '2mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + 'text_location': 'down', + 'text_align_horizontal': 'center', + 'text_align_vertical': 'top', + }, +``` + +## Example 9 (Device) +Small label + +![Cable QR Code](/docs/img/Configuration_Label_Example_09.png) + +```Python + 'device_9': { + 'title': 'Example 9 (Template for Device)', + 'font_size': '1mm', + 'label_qr_width': '9mm', + 'label_qr_height': '9mm', + 'label_qr_text_distance': '1mm', + 'label_width': '25mm', + 'label_height': '10mm', + 'label_edge_top': '0.2mm', + 'label_edge_left': '0.2mm', + 'label_edge_right': '0mm', + }, +``` + +## Example 10 (Device) - Completely self-designed + +This way, many label design options should be possible. The design of the label content is completely specified by the user via this method. + +![Cable QR Code](/docs/img/Configuration_Label_Example_10.png) + +Save this Netbox icon as an example in the following Netbox folder: +/opt/netbox/netbox/media/image-attachments/Netbox_Icon_Example.png + +![Cable QR Code](/docs/img/Netbox_Icon_Example.png) + +```Python + 'device_10': { + 'title': 'Example 10 (Template for Device)', + 'with_qr': False, + 'text_align_horizontal': 'center', + 'text_align_vertical': 'middle', + 'title': 'Example', + 'text_template': '

' + '{{ obj.name }}
' + '

' + 'Device: {{ obj.id|stringformat:"07d" }}' + '

😝 My label design 😝

', + + 'label_width': '56mm', + 'label_height': '32mm', + 'label_edge_top': '0mm', + 'label_edge_left': '0mm', + 'label_edge_right': '0mm', + }, +``` + +## Example 1 (Cable) - Cable Label vertical writing. + +This example shows how to write vertically, e.g. for cable labeling on a wide but not so high single label. + +![Cable QR Code](/docs/img/Configuration_Label_Example_11.png) + +```Python + 'cable': { + 'title': 'Example 1 (Template for Cable)', + 'with_qr': False, + 'label_edge_left': '0.00mm', + 'label_edge_right': '0.00mm', + 'label_edge_top': '0.00mm', + 'text_align_vertical': 'middle', + 'text_align_horizontal': 'center', + 'text_template': '' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '{{ obj.label }}
' + '
' + }, +``` + +## Example 2 (Cable) - Cable Label vertical writing. + +This example shows the creation of a Cabel label with barcode 128. Please note that an online function is used here (internet may be necessary). Barcode 128 is not created on the Netbox server. + +![Cable QR Code](/docs/img/Configuration_Label_Example_12.png) + +```Python + 'cable_2': { + 'title': 'Example 2 (Template for Cable)', + 'with_qr': False, + 'label_edge_left': '0.00mm', + 'label_edge_right': '0.00mm', + 'label_edge_top': '0.00mm', + 'text_align_vertical': 'middle', + 'text_align_horizontal': 'center', + + # QR-Code Image File + 'qr_version': 1, + 'qr_error_correction': 1, + 'qr_box_size': 2, + 'qr_border': 0, + + 'text_template': '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + } +``` + +[<< Back to General Configuration](README_Configuration.md) \ No newline at end of file diff --git a/docs/README_Subpages/README_Development.md b/docs/README_Subpages/README_Development.md new file mode 100644 index 0000000..7f96914 --- /dev/null +++ b/docs/README_Subpages/README_Development.md @@ -0,0 +1,49 @@ +[<< Back to README start](/README.md) +# Development +## Contributing +Developing tools for this project based on [ntc-netbox-plugin-onboarding](https://github.com/networktocode/ntc-netbox-plugin-onboarding) repo. + +Issues and pull requests are welcomed. + +## Recommended extensions for VSCode: +With the extension ID you can search for the extension more easily. +- GitHub Pull Request (Extension ID: GitHub.vscode-pull-request-github) +- Docker (Extension ID: ms-azuretools.vscode-docker) +- Python (Extension ID: ms-python.python) +- Python Debugger (Extension ID: ms-python.debugpy) +- Django (Extension ID: batisteo.vscode-django) +- Makefile-Tools (extension ID: ms-vscode.makefile-tools) + +## Easy start of the development environment +Take a look at the topic "Makefile" and pay attention to the "Makefile" file in the project to be able to quickly start Netbox with the Docker-Compose. + +## Default Login (Netbox, DB, PostgreSQL, Redis) +If the project is started via Docker-Compose, the first login to Netbox is possible with the login data from the makesuperuser.py file. +Other login data for the database, PostgreSQL and Redis can be found in the dev.env file. + +Default Netbox Login (When start via Docker-Compose): +- User: admin +- PW: admin + +## Debugging with Breakpoint in VSCode +The following describes how to start the debugging mode in VSCode. + +*Start Container* +Write "make debug-vscode" without quotation marks in terminal window of VSCode (See also Makefile) +Wait until all containers are started and Starting development server at http://0.0.0.0:8000/ is displayed. + +*Start Debug:* +- Go to "Run and Debug" and start the debug "Docker: Python -Django" (The footer of vsCode should become Orang.) +- Set a breakpoint in the code +- Open the NetBox page with the plugin. +- VSCode will stop at the stopping point. + +If you change something in an HTML file, this will be displayed immediately after a reload of the website. If you change something in a *.py file, the web server is automatically restarted after saving the file. It may be advisable to deactivate this in VSCode under File --> Auto Save so that the web server does not restart so often. + +*Helpful documentary:* +- https://medium.com/django-unleashed/debug-django-application-in-docker-container-using-vscode-ca5967340262 +- https://testdriven.io/blog/django-debugging-vs-code/ +- https://github.com/microsoft/debugpy +- https://docs.python.org/3/using/cmdline.html#cmdoption-X + +[<< Back to README start](/README.md) \ No newline at end of file diff --git a/docs/img/Configuration_Browser_Print_Settings.png b/docs/img/Configuration_Browser_Print_Settings.png new file mode 100644 index 0000000..b767a4f Binary files /dev/null and b/docs/img/Configuration_Browser_Print_Settings.png differ diff --git a/docs/img/Configuration_Label_Example_01.png b/docs/img/Configuration_Label_Example_01.png new file mode 100644 index 0000000..2e406ea Binary files /dev/null and b/docs/img/Configuration_Label_Example_01.png differ diff --git a/docs/img/Configuration_Label_Example_02.png b/docs/img/Configuration_Label_Example_02.png new file mode 100644 index 0000000..92ee978 Binary files /dev/null and b/docs/img/Configuration_Label_Example_02.png differ diff --git a/docs/img/Configuration_Label_Example_03.png b/docs/img/Configuration_Label_Example_03.png new file mode 100644 index 0000000..43d5ae7 Binary files /dev/null and b/docs/img/Configuration_Label_Example_03.png differ diff --git a/docs/img/Configuration_Label_Example_04.png b/docs/img/Configuration_Label_Example_04.png new file mode 100644 index 0000000..8573cc3 Binary files /dev/null and b/docs/img/Configuration_Label_Example_04.png differ diff --git a/docs/img/Configuration_Label_Example_05.png b/docs/img/Configuration_Label_Example_05.png new file mode 100644 index 0000000..f221a8a Binary files /dev/null and b/docs/img/Configuration_Label_Example_05.png differ diff --git a/docs/img/Configuration_Label_Example_06.png b/docs/img/Configuration_Label_Example_06.png new file mode 100644 index 0000000..ec04bc5 Binary files /dev/null and b/docs/img/Configuration_Label_Example_06.png differ diff --git a/docs/img/Configuration_Label_Example_07.png b/docs/img/Configuration_Label_Example_07.png new file mode 100644 index 0000000..58df97e Binary files /dev/null and b/docs/img/Configuration_Label_Example_07.png differ diff --git a/docs/img/Configuration_Label_Example_08.png b/docs/img/Configuration_Label_Example_08.png new file mode 100644 index 0000000..35211c2 Binary files /dev/null and b/docs/img/Configuration_Label_Example_08.png differ diff --git a/docs/img/Configuration_Label_Example_09.png b/docs/img/Configuration_Label_Example_09.png new file mode 100644 index 0000000..18f2376 Binary files /dev/null and b/docs/img/Configuration_Label_Example_09.png differ diff --git a/docs/img/Configuration_Label_Example_10.png b/docs/img/Configuration_Label_Example_10.png new file mode 100644 index 0000000..ee48583 Binary files /dev/null and b/docs/img/Configuration_Label_Example_10.png differ diff --git a/docs/img/Configuration_Label_Example_11.png b/docs/img/Configuration_Label_Example_11.png new file mode 100644 index 0000000..4edaca5 Binary files /dev/null and b/docs/img/Configuration_Label_Example_11.png differ diff --git a/docs/img/Configuration_Label_Example_12.png b/docs/img/Configuration_Label_Example_12.png new file mode 100644 index 0000000..8585935 Binary files /dev/null and b/docs/img/Configuration_Label_Example_12.png differ diff --git a/docs/img/Configuration_Printer_WordPreview.png b/docs/img/Configuration_Printer_WordPreview.png new file mode 100644 index 0000000..ca7045d Binary files /dev/null and b/docs/img/Configuration_Printer_WordPreview.png differ diff --git a/docs/img/Configuration_Printer_ZM400.png b/docs/img/Configuration_Printer_ZM400.png new file mode 100644 index 0000000..d904fd3 Binary files /dev/null and b/docs/img/Configuration_Printer_ZM400.png differ diff --git a/docs/img/Netbox_Icon_Example.png b/docs/img/Netbox_Icon_Example.png new file mode 100644 index 0000000..aaca2d2 Binary files /dev/null and b/docs/img/Netbox_Icon_Example.png differ diff --git a/netbox_qrcode/__init__.py b/netbox_qrcode/__init__.py index 6d16a96..baf6d37 100644 --- a/netbox_qrcode/__init__.py +++ b/netbox_qrcode/__init__.py @@ -10,22 +10,75 @@ class QRCodeConfig(PluginConfig): author = 'Nikolay Yuzefovich' author_email = 'mgk.kolek@gmail.com' required_settings = [] + min_version = '4.3.0' + max_version = '4.3.99' default_settings = { + + ################################## + # General Plugin + 'title': '', + + ################################## + # Text content 'with_text': True, + 'text_location': 'right', + 'text_align_horizontal': 'left', + 'text_align_vertical': 'middle', + + # Text source (Option A) 'text_fields': ['name', 'serial'], - 'font': 'TahomaBold', 'custom_text': None, - 'text_location': 'right', + + # Text source (Option B) + 'text_template': None, + + ################################## + # Font + 'font': 'TahomaBold', + 'font_size': '3mm', + 'font_weight': 'normal', + 'font_color': 'black', + + ################################## + # QR-Code + 'with_qr': True, + + # QR-Code alternative source + 'url_template': None, + + # QR-Code Image File 'qr_version': 1, 'qr_error_correction': 0, - 'qr_box_size': 6, - 'qr_border': 4, + 'qr_box_size': 4, + 'qr_border': 0, + + ################################## + # Label Layout + + # Label dimensions + 'label_qr_width': '12mm', + 'label_qr_height': '12mm', + + # Label edge + 'label_edge_top': '0mm', + 'label_edge_left': '1.5mm', + 'label_edge_right': '1.5mm', + 'label_edge_bottom': '0mm', + + # Label QR code positioning + 'label_width': '56mm', + 'label_height': '32mm', + 'label_qr_text_distance': '1mm', + + # Module-dependent configuration 'device': { 'text_fields': ['name', 'serial'] }, + 'rack': { 'text_fields': ['name'] }, + 'cable': { 'text_fields': [ '_termination_a_device', @@ -38,15 +91,22 @@ class QRCodeConfig(PluginConfig): 'b_terminations' ] }, + 'location': { 'text_fields': ['name'] }, + 'powerfeed': { - 'text_fields': ['name'], + 'text_fields': ['name'] }, + 'powerpanel': { 'text_fields': ['name'] - } + }, + + 'module': { + }, + 'logo': '', } config = QRCodeConfig # noqa E305 diff --git a/netbox_qrcode/fonts/ArialBlack.ttf b/netbox_qrcode/fonts/ArialBlack.ttf deleted file mode 100644 index 8877277..0000000 Binary files a/netbox_qrcode/fonts/ArialBlack.ttf and /dev/null differ diff --git a/netbox_qrcode/fonts/ArialMT.ttf b/netbox_qrcode/fonts/ArialMT.ttf deleted file mode 100644 index 084f062..0000000 Binary files a/netbox_qrcode/fonts/ArialMT.ttf and /dev/null differ diff --git a/netbox_qrcode/fonts/ComicSansMSBold.ttf b/netbox_qrcode/fonts/ComicSansMSBold.ttf deleted file mode 100644 index e68979c..0000000 Binary files a/netbox_qrcode/fonts/ComicSansMSBold.ttf and /dev/null differ diff --git a/netbox_qrcode/fonts/CourierNewBold.ttf b/netbox_qrcode/fonts/CourierNewBold.ttf deleted file mode 100644 index 894c455..0000000 Binary files a/netbox_qrcode/fonts/CourierNewBold.ttf and /dev/null differ diff --git a/netbox_qrcode/fonts/Tahoma.ttf b/netbox_qrcode/fonts/Tahoma.ttf deleted file mode 100644 index d204f95..0000000 Binary files a/netbox_qrcode/fonts/Tahoma.ttf and /dev/null differ diff --git a/netbox_qrcode/fonts/TahomaBold.ttf b/netbox_qrcode/fonts/TahomaBold.ttf deleted file mode 100644 index e4024e2..0000000 Binary files a/netbox_qrcode/fonts/TahomaBold.ttf and /dev/null differ diff --git a/netbox_qrcode/template_content.py b/netbox_qrcode/template_content.py index d4b44ba..3226782 100644 --- a/netbox_qrcode/template_content.py +++ b/netbox_qrcode/template_content.py @@ -1,124 +1,176 @@ from packaging import version - from django.conf import settings from django.core.exceptions import ObjectDoesNotExist -from django.template import engines - from netbox.plugins import PluginTemplateExtension +from .template_content_functions import create_text, create_url, config_for_modul, create_QRCode -from .utilities import get_img_b64, get_qr, get_qr_text, get_concat - +# ****************************************************************************************** +# Contains the main functionalities of the plugin and thus creates the content for the +# individual modules, e.g: Device, Rack etc. +# ****************************************************************************************** +################################## +# Class for creating the plugin content class QRCode(PluginTemplateExtension): - def x_page(self): - config = self.context['config'] - obj = self.context['object'] - request = self.context['request'] - url = request.build_absolute_uri(obj.get_absolute_url()) - # get object settings - obj_cfg = config.get(self.model.replace('dcim.', '')) - if obj_cfg is None: - return '' - # and ovverride default - config.update(obj_cfg) - - qr_args = {} - for k, v in config.items(): - if k.startswith('qr_'): - qr_args[k.replace('qr_', '')] = v - - qr_img = get_qr(url, **qr_args) - if config.get('with_text'): - if config.get('text_template'): - django_engine = engines["django"] - template = django_engine.from_string(config.get('text_template')) - text = template.render({'obj': obj}) - else: - text = [] - for text_field in config.get('text_fields', []): - cfn = None - if '.' in text_field: - try: - text_field, cfn = text_field.split('.') - except ValueError: - cfn = None - if getattr(obj, text_field, None): - if cfn: - try: - if getattr(obj, text_field).get(cfn): - text.append('{}'.format(getattr(obj, text_field).get(cfn))) - except AttributeError: - # fix for nb3.3: trying to get cable termination and device in same way as custom field - if type(getattr(obj, text_field)) is list: - first_element = next(iter(getattr(obj, text_field)), None) - if first_element and getattr(first_element, cfn, None): - text.append('{}'.format(getattr(first_element, cfn))) - else: - text.append('{}'.format(getattr(obj, text_field))) - custom_text = config.get('custom_text') - if custom_text: - text.append(custom_text) - text = '\n'.join(text) - text_img = get_qr_text(qr_img.size, text, config.get('font'), config.get('font_size', 0)) - qr_with_text = get_concat(qr_img, text_img, config.get('text_location', 'right')) - - img = get_img_b64(qr_with_text) - else: - img = get_img_b64(qr_img) + ################################## + # Creates a plug-in view for a label. + # -------------------------------- + # Parameter: + # labelDesignNo: Which label design should be loaded. + def Create_SubPluginContent(self, labelDesignNo): + + thisSelf = self + + obj = self.context['object'] # An object of the type Device, Rack etc. + + # Config suitable for the module + config = config_for_modul(thisSelf, labelDesignNo) + + # Abort if no config data. + if config is None: + return '' + + # Get URL for QR code + url = create_url(thisSelf, config, obj) + + # Create a QR code + qrCode = create_QRCode(url, config) + + # Create the text for the label if required. + text = create_text(config, obj, qrCode) + + # Create plugin using template try: - if version.parse(settings.VERSION).major >= 3: - return self.render( - 'netbox_qrcode/qrcode3.html', extra_context={'image': img} + if version.parse(settings.RELEASE.version).major >= 3: + + render = self.render( + 'netbox_qrcode/qrcode3.html', extra_context={ + 'title': config.get('title'), + 'labelDesignNo': labelDesignNo, + 'qrCode': qrCode, + 'with_text': config.get('with_text'), + 'text': text, + 'text_location': config.get('text_location'), + 'text_align_horizontal': config.get('text_align_horizontal'), + 'text_align_vertical': config.get('text_align_vertical'), + 'font': config.get('font'), + 'font_size': config.get('font_size'), + 'font_weight': config.get('font_weight'), + 'font_color': config.get('font_color'), + 'with_qr': config.get('with_qr'), + 'label_qr_width': config.get('label_qr_width'), + 'label_qr_height': config.get('label_qr_height'), + 'label_qr_text_distance': config.get('label_qr_text_distance'), + 'label_width': config.get('label_width'), + 'label_height': config.get('label_height'), + 'label_edge_top': config.get('label_edge_top'), + 'label_edge_left': config.get('label_edge_left'), + 'label_edge_right': config.get('label_edge_right'), + 'label_edge_bottom': config.get('label_edge_bottom') + } + ) + + return render else: + # Versions 1 and 2 are no longer supported. return self.render( - 'netbox_qrcode/qrcode.html', extra_context={'image': img} + 'netbox_qrcode/qrcode.html', extra_context={'image': qrCode} ) except ObjectDoesNotExist: return '' + ################################## + # Create plugin content + # - First, a plugin view is created for the first label. + # - If there are further configuration entries for the object/model (e.g. device, rack etc.), + # further label views are also created as additional plugin views. + def Create_PluginContent(self): + + # First Plugin Content + pluginContent = QRCode.Create_SubPluginContent(self, 1) + + # Check whether there is another configuration for the object, e.g. device, rack, etc. + # Support up to 10 additional label configurations (objectName_2 to ..._10) per object (e.g. device, rack, etc.). + + config = self.context['config'] # Django configuration + for i in range(2, 11): + + configName = self.models[0].replace('dcim.', '') + '_' + str(i) + obj_cfg = config.get(configName) # Load configuration for additional label if possible. + + if(obj_cfg): + pluginContent += QRCode.Create_SubPluginContent(self, i) # Add another plugin view + else: + break + + return pluginContent + +################################## +# The following section serves to integrate the plugin into Netbox Core. + +# Class for creating a QR code for the model: Device class DeviceQRCode(QRCode): - model = 'dcim.device' + models = ('dcim.device',) def right_page(self): - return self.x_page() - + return self.Create_PluginContent() +# Class for creating a QR code for the model: Rack class RackQRCode(QRCode): - model = 'dcim.rack' + models = ('dcim.rack',) def right_page(self): - return self.x_page() - + return self.Create_PluginContent() +# Class for creating a QR code for the model: Cable class CableQRCode(QRCode): - model = 'dcim.cable' + models = ('dcim.cable',) def left_page(self): - return self.x_page() - + return self.Create_PluginContent() +# Class for creating a QR code for the model: Location class LocationQRCode(QRCode): - model = 'dcim.location' + models = ('dcim.location',) def left_page(self): - return self.x_page() - + return self.Create_PluginContent() +# Class for creating a QR code for the model: Power Feed class PowerFeedQRCode(QRCode): - model = 'dcim.powerfeed' + models = ('dcim.powerfeed',) def right_page(self): - return self.x_page() - + return self.Create_PluginContent() +# Class for creating a QR code for the model: Power Panel class PowerPanelQRCode(QRCode): - model = 'dcim.powerpanel' + models = ('dcim.powerpanel',) def right_page(self): - return self.x_page() + return self.Create_PluginContent() +# Class for dcim.module +class ModuleQRCode(QRCode): + models = ('dcim.module',) -template_extensions = [DeviceQRCode, RackQRCode, CableQRCode, LocationQRCode, PowerFeedQRCode, PowerPanelQRCode] + def right_page(self): + return self.Create_PluginContent() + +################################## +# Other plugins support + +# Commenting out (for now) - make this work on core models first. +# Class for creating a QR code for the Plugin: Netbox-Inventory (https://github.com/ArnesSI/netbox-inventory) +#class Plugin_Netbox_Inventory(QRCode): +# models = ()'netbox_inventory.asset' # Info for Netbox in which model the plugin should be integrated. +# +# def right_page(self): +# return self.Create_PluginContent() + +# Connects Netbox Core with the plug-in classes +# Removed , Plugin_Netbox_Inventory] +template_extensions = [DeviceQRCode, ModuleQRCode, RackQRCode, CableQRCode, LocationQRCode, PowerFeedQRCode, PowerPanelQRCode] \ No newline at end of file diff --git a/netbox_qrcode/template_content_functions.py b/netbox_qrcode/template_content_functions.py new file mode 100644 index 0000000..95774c5 --- /dev/null +++ b/netbox_qrcode/template_content_functions.py @@ -0,0 +1,146 @@ +from .utilities import get_img_b64, get_qr +from django.template import engines + +# ****************************************************************************************** +# For better clarity, the sub-functions of template_content.py have been outsourced. +# ****************************************************************************************** + +################################## +# The configuration is taken and all fields that are module-specific (e.g. Device, Rack, etc.) are replaced. +# -------------------------------- +# Parameter: +# labelDesignNo: Which label design should be loaded. +# parentSelf: Self from Parrent Function +def config_for_modul(parentSelf, labelDesignNo): + + # Copy so that the Runtime data is not changed. + config = parentSelf.context['config'].copy() # From Netbox Config File + + # Create suffix to read the correct module configuration. + confModulsufix = str() # None if the first standard label + + if(labelDesignNo >= 2): + confModulsufix = '_' + str(labelDesignNo) + + # Collect the QR code plugin configuration for the specific object such as device, rack etc. + # and overwrite the default configuration fields. + obj_cfg = config.get(parentSelf.models[0].replace('dcim.', '') + confModulsufix) # get spezific object settings + + if obj_cfg is not None: + config.update(obj_cfg) # Ovverride default confiv Values + return config + else: + return config # No module customisation + +################################## +# Create QR-Code +# -------------------------------- +# Parameter: +# text: Text for QR-Code +# config: From the Netbox configuration file +def create_QRCode(text, config): + + # Collect the configuration entries that begin with "qr_. + # These are required to generate the QR code. + qr_args = {} + for k, v in config.items(): + if k.startswith('qr_'): + qr_args[k.replace('qr_', '')] = v + + # Create a QR code + qrCode = get_qr(text, **qr_args) + return get_img_b64(qrCode) + + +################################## +# Create URL for QR code +# -------------------------------- +# Parameter: +# config: From the Netbox configuration file +# obj: Data from the model (e.g. device, rack, etc.) +# request: HTML Request Information +def create_url(parentSelf, config, obj): + + request = parentSelf.context['request'] # HTML Request Informations + + if config.get('url_template'): + # A user-defined design specification of the URL is provided in ninja2 format. + django_engine = engines["django"] + template = django_engine.from_string(config.get('url_template')) # Custom template for URL design. + return template.render({'obj': obj}) # Replace placeholder + else: + return request.build_absolute_uri(obj.get_absolute_url()) # URL to the requested page + +################################## +# Create text for label +# -------------------------------- +# Parameter: +# config: From the Netbox configuration file +# obj: Data from the model (e.g. device, rack, etc.) +# qrCode: QR-Code Image +def create_text(config, obj, qrCode): + + text = str() + + if config.get('with_text'): + if config.get('text_template'): + return get_text_template(config, obj, qrCode) # Create text content based on the Ninja2 template from the user + else: + return get_text_fields(config, obj) # Use the list of variables from the Config. + +################################## +# A user-defined design specification of the text is provided in ninja2 format. +# -------------------------------- +# Parameter: +# config: From the Netbox configuration file +# obj: Data from the model (e.g. device, rack, etc.) +# qrCode: QR-Code Image (To create a freely defined label with QR code.) +def get_text_template(config, obj, qrCode): + + django_engine = engines["django"] + template = django_engine.from_string(config.get('text_template')) # Get Custom Template + logo = config.get('logo') + return template.render({'obj': obj, + 'logo': logo, + 'qrCode': qrCode}) # Replace placeholder + +################################## +# Retrieves all values from the object (e.g. device, rack, etc.) +# depending on the configuration parameter that are to be displayed in list form and prepares them. +# -------------------------------- +# Parameter: +# config: From the Netbox configuration file +# obj: Data from the model (e.g. device, rack, etc.) +def get_text_fields(config, obj): + + text = [] + + for text_field in config.get('text_fields', []): + cfn = None + if '.' in text_field: + try: + text_field, cfn = text_field.split('.') + except ValueError: + cfn = None + if getattr(obj, text_field, None): + if cfn: + try: + if getattr(obj, text_field).get(cfn): + text.append('{}'.format(getattr(obj, text_field).get(cfn))) + except AttributeError: + # fix for nb3.3: trying to get cable termination and device in same way as custom field + if type(getattr(obj, text_field)) is list: + first_element = next(iter(getattr(obj, text_field)), None) + if first_element and getattr(first_element, cfn, None): + text.append('{}'.format(getattr(first_element, cfn))) + else: + text.append('{}'.format(getattr(obj, text_field))) + + # Append user-defined text to the end. + custom_text = config.get('custom_text') + + if custom_text: + text.append(custom_text) + + # Convert text list to string with line breaks. + return '
'.join(text) \ No newline at end of file diff --git a/netbox_qrcode/templates/netbox_qrcode/qrcode3.html b/netbox_qrcode/templates/netbox_qrcode/qrcode3.html index 88d4629..79e96b4 100644 --- a/netbox_qrcode/templates/netbox_qrcode/qrcode3.html +++ b/netbox_qrcode/templates/netbox_qrcode/qrcode3.html @@ -1,19 +1,165 @@ + + +
- QR Code + OR-Code (H/W: {{label_height}} x {{label_width}}) {% if title %} - {{title}}{% endif %}
- +
+
+ + {# Only Text label #} + {% if with_text is True and with_qr is False %} + + + + +
+ {{text|safe|escape}} +
+ {% endif %} + + {# Text and QR-Code #} + {% if with_text is True and with_qr is True %} + + {# Horizontal label #} + {% if text_location == "right" or text_location == "left" %} + + + + {% if text_location == "right" %} + + {% endif %} + + + + {% if text_location == "left" %} + + {% endif %} + + +
+ {% include "netbox_qrcode/qrcode3_sub_qrcode.html" %} + +
+ + {{text|safe|escape}} + +
+
+ {% include "netbox_qrcode/qrcode3_sub_qrcode.html" %} +
+ {% endif %} + + {# Vertical label #} + {% if with_qr and text_location == "up" or text_location == "down" %} + + + {% if text_location == "down" %} + + + + {% endif %} + + + + + + {% if text_location == "up" %} + + + + {% endif %} + +
+ {% include "netbox_qrcode/qrcode3_sub_qrcode.html" %} +
+
+ + {{text|safe|escape}} + +
+
+ {% include "netbox_qrcode/qrcode3_sub_qrcode.html" %} +
+ {% endif %} + {% endif %} + + {# Only QR-Code label #} + {% if with_text is False and with_qr is True %} +
+ {% include "netbox_qrcode/qrcode3_sub_qrcode.html" %} +
+ {% endif %} +
+
+ diff --git a/netbox_qrcode/templates/netbox_qrcode/qrcode3_sub_qrcode.html b/netbox_qrcode/templates/netbox_qrcode/qrcode3_sub_qrcode.html new file mode 100644 index 0000000..eaf7204 --- /dev/null +++ b/netbox_qrcode/templates/netbox_qrcode/qrcode3_sub_qrcode.html @@ -0,0 +1,13 @@ +
+ +
\ No newline at end of file diff --git a/netbox_qrcode/utilities.py b/netbox_qrcode/utilities.py index c1e2f7c..e0cd376 100644 --- a/netbox_qrcode/utilities.py +++ b/netbox_qrcode/utilities.py @@ -1,18 +1,17 @@ import base64 import qrcode - from io import BytesIO -from PIL import Image, ImageFont, ImageDraw - -from pkg_resources import resource_stream - - -def get_qr_with_text(qr, descr): - dsi = get_qr_text(qr.size, descr) - resimg = get_concat(qr, dsi) - return get_img_b64(resimg) +# ****************************************************************************************** +# Includes useful tools to create the content. +# ****************************************************************************************** +################################## +# Creates a QR code as an image.: https://pypi.org/project/qrcode/3.0/ +# -------------------------------- +# Parameter: +# text: Text to be included in the QR code. +# **kwargs: List of parameters which properties the QR code should have. (e.g. version, box_size, error_correction, border etc.) def get_qr(text, **kwargs): qr = qrcode.QRCode(**kwargs) qr.add_data(text) @@ -21,94 +20,12 @@ def get_qr(text, **kwargs): img = img.get_image() return img - +################################## +# Converts an image to Base64 +# -------------------------------- +# Parameter: +# img: Image file def get_img_b64(img): stream = BytesIO() img.save(stream, format='png') - return str(base64.b64encode(stream.getvalue()), encoding='ascii') - - -def get_qr_text(max_size, text, font='TahomaBold', font_size=0): - tmpimg = Image.new('P', max_size, 'white') - - if font_size == 0: - text_too_large = True - font_size = 56 - while text_too_large: - file_path = resource_stream(__name__, 'fonts/{}.ttf'.format(font)) - try: - fnt = ImageFont.truetype(file_path, font_size) - except Exception: - fnt = ImageFont.load_default() - - draw = ImageDraw.Draw(tmpimg) - left, top, w, h = draw.textbbox((0, 0), text=text, font=fnt) - if w < max_size[0] - 4 and h < max_size[1] - 4: - text_too_large = False - font_size -= 1 - else: - file_path = resource_stream(__name__, 'fonts/{}.ttf'.format(font)) - try: - fnt = ImageFont.truetype(file_path, font_size) - except Exception: - fnt = ImageFont.load_default() - draw = ImageDraw.Draw(tmpimg) - left, top, w, h = draw.textbbox((0, 0), text=text, font=fnt) - - img = Image.new('P', (w, h), 'white') - draw = ImageDraw.Draw(img) - draw.text((0, 0), text, font=fnt, fill='black') - return img - - -def get_concat(im1, im2, direction='right'): - if direction == 'right' or direction == 'left': - width = im1.width + im2.width - height = max(im1.height, im2.height) - elif direction == 'down' or direction == 'up': - width = max(im1.width, im2.width) - height = im1.height + im2.height - else: - raise ValueError( - 'Invalid direction "{}" (must be one of "left", "right", "up", or "down")'.format(direction) - ) - - dst = Image.new('L', (width, height), 'white') - - if direction == 'right' or direction == 'left': - if im1.height > im2.height: - im1_y = 0 - im2_y = abs(im1.height-im2.height) // 2 - else: - im1_y = abs(im1.height-im2.height) // 2 - im2_y = 0 - - if direction == 'right': - im1_x = 0 - im2_x = im1.width - else: - im1_x = im2.width - im2_x = 0 - elif direction == 'up' or direction == 'down': - if im1.width > im2.width: - im1_x = 0 - im2_x = abs(im1.width-im2.width) // 2 - else: - im1_x = abs(im1.width-im2.width) // 2 - im2_x = 0 - - if direction == 'down': - im1_y = 0 - im2_y = im1.height - else: - im1_y = im2.height - im2_y = 0 - else: - raise ValueError( - 'Invalid direction "{}" (must be one of "left", "right", "up", or "down")'.format(direction) - ) - - dst.paste(im1, (im1_x, im1_y)) - dst.paste(im2, (im2_x, im2_y)) - - return dst + return str(base64.b64encode(stream.getvalue()), encoding='ascii') \ No newline at end of file diff --git a/netbox_qrcode/version.py b/netbox_qrcode/version.py index 4ae81f3..f18e5d0 100644 --- a/netbox_qrcode/version.py +++ b/netbox_qrcode/version.py @@ -1 +1 @@ -__version__ = "0.0.13" +__version__ = "0.0.18" diff --git a/setup.py b/setup.py index 0de92e9..a1c6140 100644 --- a/setup.py +++ b/setup.py @@ -27,13 +27,13 @@ def get_version(rel_path): description='QR Code generation for netbox objects', long_description=long_description, long_description_content_type="text/markdown", - url='https://github.com/k01ek/netbox-qrcode', + url='https://github.com/netbox-community/netbox-qrcode', author='Nikolay Yuzefovich', author_email='mgk.kolek@gmail.com', packages=find_packages(), include_package_data=True, - min_version='4.0.2', - max_version='4.0.10', + min_version='4.3.0', + max_version='4.3.99', package_data={ '': ['*.ttf'], '': ['*.html'],