Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a02acbc561 | ||
|
|
74d648b94d | ||
|
|
e9422d70e3 | ||
|
|
15b61fd7f9 | ||
|
|
c264536850 | ||
|
|
2dbcaa4877 | ||
|
|
6884bb3b94 | ||
|
|
bf47320864 | ||
|
|
f7bb83c913 | ||
|
|
0519796af6 | ||
|
|
19fc473286 | ||
|
|
f4188b957f | ||
|
|
51736942e7 | ||
|
|
bf63046edb | ||
|
|
72274e59b5 | ||
|
|
ae06bea12d | ||
|
|
6937a0ecca | ||
|
|
e7eb5b46e4 | ||
|
|
154c0de9f4 | ||
|
|
c84aa3650b | ||
|
|
fb29bc4258 | ||
|
|
7e59a5644b | ||
|
|
f1dbedcfba | ||
|
|
bbb00d667d | ||
|
|
743759f0e6 | ||
|
|
3ed18c5767 | ||
|
|
e926f01141 | ||
|
|
bc0b77f962 | ||
|
|
cec3d50313 | ||
|
|
d70f8afc41 | ||
|
|
0122275ee1 | ||
|
|
267cb8db63 | ||
|
|
adea4437c9 |
@@ -0,0 +1,13 @@
|
||||
Copyright 2022 ICTU
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -6,6 +6,7 @@ name = "pypi"
|
||||
[packages]
|
||||
|
||||
[dev-packages]
|
||||
twine = "3.7.1"
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
|
||||
Generated
+192
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f3cebe84c169ba6773cfcfae65b47e5076fa331e445f47a6ec4d13306942deb4"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {},
|
||||
"develop": {
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da",
|
||||
"sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
|
||||
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||
],
|
||||
"version": "==2021.10.8"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd",
|
||||
"sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==2.0.10"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c",
|
||||
"sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.18.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==3.3"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6",
|
||||
"sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==4.10.0"
|
||||
},
|
||||
"keyring": {
|
||||
"hashes": [
|
||||
"sha256:9012508e141a80bd1c0b6778d5c610dd9f8c464d75ac6774248500503f972fb9",
|
||||
"sha256:b0d28928ac3ec8e42ef4cc227822647a19f1d544f21f96457965dc01cf555261"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==23.5.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==21.3"
|
||||
},
|
||||
"pkginfo": {
|
||||
"hashes": [
|
||||
"sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff",
|
||||
"sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc"
|
||||
],
|
||||
"version": "==1.8.2"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:59b895e326f0fb0d733fd28c6839bd18ad0687ba20efc26d4277fd1d30b971f4",
|
||||
"sha256:9135c1af61eec0f650cd1ea1ed8ce298e54d56bcd8cc2ef46edd7702c171337c"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
|
||||
"sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.6"
|
||||
},
|
||||
"readme-renderer": {
|
||||
"hashes": [
|
||||
"sha256:a50a0f2123a4c1145ac6f420e1a348aafefcc9211c846e3d51df05fe3d865b7d",
|
||||
"sha256:b512beafa6798260c7d5af3e1b1f097e58bfcd9a575da7c4ddd5e037490a5b85"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==32.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5",
|
||||
"sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==2.27.0"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
"hashes": [
|
||||
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
|
||||
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
|
||||
],
|
||||
"version": "==0.9.1"
|
||||
},
|
||||
"rfc3986": {
|
||||
"hashes": [
|
||||
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
|
||||
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c",
|
||||
"sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.62.3"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
"sha256:28460a3db6b4532bde6a5db6755cf2dce6c5020bada8a641bb2c5c7a9b1f35b8",
|
||||
"sha256:8c120845fc05270f9ee3e9d7ebbed29ea840e41f48cd059e04733f7e1d401345"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
|
||||
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.7"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d",
|
||||
"sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.7.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
## Software Lifecycle Management
|
||||
|
||||
### _Netbox Plugin for lifecycle management of software components, including versions and installations_
|
||||
|
||||
---
|
||||
|
||||
### Installation Guide
|
||||
|
||||
Depending on running in Docker (see the netbox-docker repository) add netbox_slm to the plugin_requirements.txt (requires a valid pypi version)
|
||||
|
||||
```
|
||||
netbox-slm==0.1
|
||||
```
|
||||
|
||||
Change the configuration.py to include the plugin:
|
||||
|
||||
```
|
||||
PLUGINS = [
|
||||
'netbox_slm',
|
||||
]
|
||||
```
|
||||
|
||||
### Releasing Guide
|
||||
|
||||
_TODO_
|
||||
|
||||
### Developer Guide (local installation)
|
||||
|
||||
_Follow the steps below on your local system to run netbox and the netbox_slm plugin in developer mode_
|
||||
|
||||
### Setup
|
||||
|
||||
The goal below is to run all Netbox components in Docker and run a local Netbox Django copy with auto-reload to develop the plugin pointing to the Dockerized postgres and redis instances, basically ignoring the netbox docker runtime server.
|
||||
|
||||
### Gotcha's
|
||||
|
||||
_Netbox's exception handler seems to supress initial Exceptions, obfuscating missing imports or other Django related issues by re-raising an opague error, e.g. netbox_slm plugin namespace doesnotexist. It's recommended to often check the runtime server or patch Netbox's global exception handler._
|
||||
|
||||
### Steps
|
||||
|
||||
```
|
||||
from your projects directory clone the netbox repository
|
||||
|
||||
$ git clone https://github.com/netbox-community/netbox
|
||||
$ cd netbox
|
||||
$ git checkout f5356b8 (to pin 3.0.9)
|
||||
|
||||
install the virtual environment
|
||||
|
||||
$ pipenv shell
|
||||
$ pipenv install
|
||||
|
||||
create and edit netbox/configuration.py (based on the template file) add these lines at the end of the file;
|
||||
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'dummy'
|
||||
DEVELOPER = True
|
||||
PLUGINS = [
|
||||
'netbox_slm',
|
||||
]
|
||||
````
|
||||
|
||||
The Netbox installation above will be used to run Django management commands like runserver, makemigrations and migrate, which will be explained in the next steps below;
|
||||
|
||||
```
|
||||
from your projects directory clone the netbox-slm repository
|
||||
|
||||
$ git clone https://github.com/ICTU/netbox_slm
|
||||
$ cd netbox_slm
|
||||
$ ./start-netbox.sh
|
||||
````
|
||||
|
||||
This will start Netbox locally (requires Docker) and forward the redis and postgres ports to the localhost (make sure there's no processes using these ports or change the dockerfiles accordingly)
|
||||
|
||||
Note, you can also start and stop netbox by hand:
|
||||
```
|
||||
$ cd netbox-docker
|
||||
$ docker-compose up -d
|
||||
|
||||
or stop the stack with
|
||||
|
||||
$ docker-compose down
|
||||
|
||||
# to start fresh:
|
||||
|
||||
$ docker-compose down
|
||||
$ docker volume rm netbox-docker_netbox-postgres-data # et cetera
|
||||
$ docker-compose up -d --force-recreate
|
||||
|
||||
this will require you to re-run the migrate commando's for netbox-slm, see further down below
|
||||
```
|
||||
|
||||
Go back to the netbox configuration.py file and update the postgres and redis connection strings (username, password) to the ones the netbox docker backend is using, for example (using default user and passwords from the netbox docker example):
|
||||
|
||||
```
|
||||
<<collapsed>>
|
||||
|
||||
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
||||
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
'CONN_MAX_AGE': 300, # Max database connection age
|
||||
}
|
||||
|
||||
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
||||
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
||||
# to use two separate database IDs.
|
||||
REDIS = {
|
||||
'tasks': {
|
||||
'HOST': 'localhost',
|
||||
'PORT': 6379,
|
||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||
# 'SENTINEL_SERVICE': 'netbox',
|
||||
'PASSWORD': 'H733Kdjndks81',
|
||||
'DATABASE': 0,
|
||||
'SSL': False,
|
||||
# Set this to True to skip TLS certificate verification
|
||||
# This can expose the connection to attacks, be careful
|
||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||
},
|
||||
'caching': {
|
||||
'HOST': 'localhost',
|
||||
'PORT': 6379,
|
||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||
# 'SENTINEL_SERVICE': 'netbox',
|
||||
'PASSWORD': 'H733Kdjndks81',
|
||||
'DATABASE': 1,
|
||||
'SSL': False,
|
||||
# Set this to True to skip TLS certificate verification
|
||||
# This can expose the connection to attacks, be careful
|
||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||
}
|
||||
}
|
||||
|
||||
<<collapsed>>
|
||||
```
|
||||
|
||||
Now you can run commands from the netbox repository like this;
|
||||
|
||||
```
|
||||
$ cd netbox/netbox
|
||||
$ export PYTHONPATH=../../netbox-slm/src/
|
||||
$ python3 manage.py migrate netbox_slm
|
||||
$ python3 manage.py runserver 8001
|
||||
```
|
||||
|
||||
Visit http://127.0.0.1:8001 in the browesr to see the auto reloading version of the netbox UI. Port 8000 is taken by the docker ran variant.
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
Software Lifecycle Management
|
||||
-----------------------------
|
||||
|
||||
*Netbox Plugin for lifecycle management of software components, including versions and installations*
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
--------------
|
||||
|
||||
Installation Guide
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
WARNING: This plugin requires Netbox 3.1.X or higher!
|
||||
|
||||
When using the Docker version of Netbox, first follow the `netbox-docker
|
||||
quickstart <https://github.com/netbox-community/netbox-docker#quickstart>`__
|
||||
instructions to clone the netbox-docker repo and set up the
|
||||
``docker-compose.override.yml``.
|
||||
|
||||
Next, follow these instructions (based on the `Netbox docker variant
|
||||
instructions <https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files>`__)
|
||||
to install the Netbox SLM plugin:
|
||||
|
||||
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
|
||||
``configuration/extra.py``.
|
||||
2. Create a ``plugin_requirements.txt`` with ``netbox-slm==0.95`` as
|
||||
contents.
|
||||
3. Create a ``Dockerfile-SLM`` with contents:
|
||||
|
||||
.. code:: dockerfile
|
||||
|
||||
FROM netboxcommunity/netbox:v3.X.X
|
||||
|
||||
COPY ./plugin_requirements.txt /
|
||||
RUN /opt/netbox/venv/bin/pip install --no-warn-script-location -r /plugin_requirements.txt
|
||||
|
||||
4. Create a ``docker-compose.override.yml`` with contents:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
version: '3.4'
|
||||
services:
|
||||
netbox:
|
||||
ports:
|
||||
- 8000:8080
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-SLM
|
||||
image: netbox:slm
|
||||
netbox-worker:
|
||||
image: netbox:slm
|
||||
netbox-housekeeping:
|
||||
image: netbox:slm
|
||||
|
||||
Now, build the image: ``docker compose build --no-cache``
|
||||
|
||||
And finally, run Netbox with the SLM plugin: ``docker compose up``
|
||||
|
||||
Releasing Guide
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
To draft a release;
|
||||
|
||||
update the setup.py file to reflect the new version, then from the *src*
|
||||
directory run
|
||||
|
||||
::
|
||||
|
||||
# make sure to update the version in netbox_slm/__init__.py
|
||||
$ python setup.py sdist
|
||||
$ twine upload dist/*
|
||||
|
||||
On Github.com create a similar tag and version. These steps could be
|
||||
automated with a github workflow.
|
||||
|
||||
n.b. Currently the plugin is configured to use a personal pypi account,
|
||||
this should be changed.
|
||||
|
||||
Developer Guide (local installation)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*Follow the steps below on your local system to run netbox and the
|
||||
netbox_slm plugin in developer mode*
|
||||
|
||||
Setup
|
||||
~~~~~
|
||||
|
||||
The goal below is to run all Netbox components in Docker and run a local
|
||||
Netbox Django copy with auto-reload to develop the plugin pointing to
|
||||
the Dockerized postgres and redis instances, basically ignoring the
|
||||
netbox docker runtime server.
|
||||
|
||||
Steps
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
from your projects directory clone the netbox repository
|
||||
|
||||
$ git clone https://github.com/netbox-community/netbox
|
||||
$ cd netbox
|
||||
|
||||
install the virtual environment
|
||||
|
||||
$ pipenv shell
|
||||
$ pipenv install
|
||||
|
||||
create and edit netbox/configuration.py (based on the template file) add these lines at the end of the file;
|
||||
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'dummy'
|
||||
DEVELOPER = True
|
||||
PLUGINS = [
|
||||
'netbox_slm',
|
||||
]
|
||||
|
||||
The Netbox installation above will be used to run Django management
|
||||
commands like runserver, makemigrations and migrate, which will be
|
||||
explained in the next steps below;
|
||||
|
||||
::
|
||||
|
||||
from your projects directory clone the netbox-slm repository
|
||||
|
||||
$ git clone https://github.com/ICTU/netbox_slm
|
||||
$ cd netbox_slm
|
||||
$ ./start-netbox.sh
|
||||
|
||||
This will start Netbox locally (requires Docker) and forward the redis
|
||||
and postgres ports to the localhost (make sure there’s no processes
|
||||
using these ports or change the dockerfiles accordingly)
|
||||
|
||||
Note, you can also start and stop netbox by hand:
|
||||
|
||||
::
|
||||
|
||||
$ cd netbox-docker
|
||||
$ docker-compose up -d
|
||||
|
||||
or stop the stack with
|
||||
|
||||
$ docker-compose down
|
||||
|
||||
# to start fresh:
|
||||
|
||||
$ docker-compose down
|
||||
$ docker volume rm netbox-docker_netbox-postgres-data # et cetera
|
||||
$ docker-compose up -d --force-recreate
|
||||
|
||||
this will require you to re-run the migrate commando's for netbox-slm, see further down below
|
||||
|
||||
Go back to the netbox configuration.py file and update the postgres and
|
||||
redis connection strings (username, password) to the ones the netbox
|
||||
docker backend is using, for example (using default user and passwords
|
||||
from the netbox docker example):
|
||||
|
||||
::
|
||||
|
||||
<<collapsed>>
|
||||
|
||||
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
||||
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
'CONN_MAX_AGE': 300, # Max database connection age
|
||||
}
|
||||
|
||||
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
||||
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
||||
# to use two separate database IDs.
|
||||
REDIS = {
|
||||
'tasks': {
|
||||
'HOST': 'localhost',
|
||||
'PORT': 6379,
|
||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||
# 'SENTINEL_SERVICE': 'netbox',
|
||||
'PASSWORD': 'H733Kdjndks81',
|
||||
'DATABASE': 0,
|
||||
'SSL': False,
|
||||
# Set this to True to skip TLS certificate verification
|
||||
# This can expose the connection to attacks, be careful
|
||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||
},
|
||||
'caching': {
|
||||
'HOST': 'localhost',
|
||||
'PORT': 6379,
|
||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||
# 'SENTINEL_SERVICE': 'netbox',
|
||||
'PASSWORD': 'H733Kdjndks81',
|
||||
'DATABASE': 1,
|
||||
'SSL': False,
|
||||
# Set this to True to skip TLS certificate verification
|
||||
# This can expose the connection to attacks, be careful
|
||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||
}
|
||||
}
|
||||
|
||||
<<collapsed>>
|
||||
|
||||
Now you can run commands from the netbox repository like this;
|
||||
|
||||
::
|
||||
|
||||
$ cd netbox/netbox
|
||||
$ export PYTHONPATH=../../netbox-slm/src/
|
||||
$ python3 manage.py migrate netbox_slm
|
||||
$ python3 manage.py runserver 8001
|
||||
|
||||
Visit http://127.0.0.1:8001 in the browesr to see the auto reloading
|
||||
version of the netbox UI. Port 8000 is taken by the docker ran variant.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
Copyright 2022 ICTU
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,3 @@
|
||||
include ../LICENSE
|
||||
include ../README.rst
|
||||
recursive-include netbox_slm/templates *
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
Software Lifecycle Management
|
||||
-----------------------------
|
||||
|
||||
*Netbox Plugin for lifecycle management of software components, including versions and installations*
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
--------------
|
||||
|
||||
Installation Guide
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When using the Docker version of Netbox, first follow the `netbox-docker
|
||||
quickstart <https://github.com/netbox-community/netbox-docker#quickstart>`__
|
||||
instructions to clone the netbox-docker repo and set up the
|
||||
``docker-compose.override.yml``.
|
||||
|
||||
Next, follow these instructions (based on the `Netbox docker variant
|
||||
instructions <https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files>`__)
|
||||
to install the Netbox SLM plugin:
|
||||
|
||||
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
|
||||
``configuration/extra.py``.
|
||||
2. Create a ``plugin_requirements.txt`` with ``netbox-slm==0.94`` as
|
||||
contents.
|
||||
3. Create a ``Dockerfile-SLM`` with contents:
|
||||
|
||||
.. code:: dockerfile
|
||||
|
||||
FROM netboxcommunity/netbox:v3.X.X
|
||||
|
||||
COPY ./plugin_requirements.txt /
|
||||
RUN /opt/netbox/venv/bin/pip install --no-warn-script-location -r /plugin_requirements.txt
|
||||
|
||||
4. Create a ``docker-compose.override.yml`` with contents:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
version: '3.4'
|
||||
services:
|
||||
netbox:
|
||||
ports:
|
||||
- 8000:8080
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-SLM
|
||||
image: netbox:slm
|
||||
netbox-worker:
|
||||
image: netbox:slm
|
||||
netbox-housekeeping:
|
||||
image: netbox:slm
|
||||
|
||||
Now, build the image: ``docker compose build --no-cache``
|
||||
|
||||
And finally, run Netbox with the SLM plugin: ``docker compose up``
|
||||
|
||||
Releasing Guide
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
To draft a release;
|
||||
|
||||
update the setup.py file to reflect the new version, then from the *src*
|
||||
directory run
|
||||
|
||||
::
|
||||
|
||||
# make sure to update the version in netbox_slm/__init__.py
|
||||
$ python setup.py sdist
|
||||
$ twine upload dist/*
|
||||
|
||||
On Github.com create a similar tag and version. These steps could be
|
||||
automated with a github workflow.
|
||||
|
||||
n.b. Currently the plugin is configured to use a personal pypi account,
|
||||
this should be changed.
|
||||
|
||||
Developer Guide (local installation)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*Follow the steps below on your local system to run netbox and the
|
||||
netbox_slm plugin in developer mode*
|
||||
|
||||
Setup
|
||||
~~~~~
|
||||
|
||||
The goal below is to run all Netbox components in Docker and run a local
|
||||
Netbox Django copy with auto-reload to develop the plugin pointing to
|
||||
the Dockerized postgres and redis instances, basically ignoring the
|
||||
netbox docker runtime server.
|
||||
|
||||
Steps
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
from your projects directory clone the netbox repository
|
||||
|
||||
$ git clone https://github.com/netbox-community/netbox
|
||||
$ cd netbox
|
||||
|
||||
install the virtual environment
|
||||
|
||||
$ pipenv shell
|
||||
$ pipenv install
|
||||
|
||||
create and edit netbox/configuration.py (based on the template file) add these lines at the end of the file;
|
||||
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'dummy'
|
||||
DEVELOPER = True
|
||||
PLUGINS = [
|
||||
'netbox_slm',
|
||||
]
|
||||
|
||||
The Netbox installation above will be used to run Django management
|
||||
commands like runserver, makemigrations and migrate, which will be
|
||||
explained in the next steps below;
|
||||
|
||||
::
|
||||
|
||||
from your projects directory clone the netbox-slm repository
|
||||
|
||||
$ git clone https://github.com/ICTU/netbox_slm
|
||||
$ cd netbox_slm
|
||||
$ ./start-netbox.sh
|
||||
|
||||
This will start Netbox locally (requires Docker) and forward the redis
|
||||
and postgres ports to the localhost (make sure there’s no processes
|
||||
using these ports or change the dockerfiles accordingly)
|
||||
|
||||
Note, you can also start and stop netbox by hand:
|
||||
|
||||
::
|
||||
|
||||
$ cd netbox-docker
|
||||
$ docker-compose up -d
|
||||
|
||||
or stop the stack with
|
||||
|
||||
$ docker-compose down
|
||||
|
||||
# to start fresh:
|
||||
|
||||
$ docker-compose down
|
||||
$ docker volume rm netbox-docker_netbox-postgres-data # et cetera
|
||||
$ docker-compose up -d --force-recreate
|
||||
|
||||
this will require you to re-run the migrate commando's for netbox-slm, see further down below
|
||||
|
||||
Go back to the netbox configuration.py file and update the postgres and
|
||||
redis connection strings (username, password) to the ones the netbox
|
||||
docker backend is using, for example (using default user and passwords
|
||||
from the netbox docker example):
|
||||
|
||||
::
|
||||
|
||||
<<collapsed>>
|
||||
|
||||
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
||||
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
'CONN_MAX_AGE': 300, # Max database connection age
|
||||
}
|
||||
|
||||
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
||||
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
||||
# to use two separate database IDs.
|
||||
REDIS = {
|
||||
'tasks': {
|
||||
'HOST': 'localhost',
|
||||
'PORT': 6379,
|
||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||
# 'SENTINEL_SERVICE': 'netbox',
|
||||
'PASSWORD': 'H733Kdjndks81',
|
||||
'DATABASE': 0,
|
||||
'SSL': False,
|
||||
# Set this to True to skip TLS certificate verification
|
||||
# This can expose the connection to attacks, be careful
|
||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||
},
|
||||
'caching': {
|
||||
'HOST': 'localhost',
|
||||
'PORT': 6379,
|
||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||
# 'SENTINEL_SERVICE': 'netbox',
|
||||
'PASSWORD': 'H733Kdjndks81',
|
||||
'DATABASE': 1,
|
||||
'SSL': False,
|
||||
# Set this to True to skip TLS certificate verification
|
||||
# This can expose the connection to attacks, be careful
|
||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||
}
|
||||
}
|
||||
|
||||
<<collapsed>>
|
||||
|
||||
Now you can run commands from the netbox repository like this;
|
||||
|
||||
::
|
||||
|
||||
$ cd netbox/netbox
|
||||
$ export PYTHONPATH=../../netbox-slm/src/
|
||||
$ python3 manage.py migrate netbox_slm
|
||||
$ python3 manage.py runserver 8001
|
||||
|
||||
Visit http://127.0.0.1:8001 in the browesr to see the auto reloading
|
||||
version of the netbox UI. Port 8000 is taken by the docker ran variant.
|
||||
@@ -5,7 +5,7 @@ class SLMConfig(PluginConfig):
|
||||
name = 'netbox_slm'
|
||||
verbose_name = 'Software Lifecycle Management'
|
||||
description = 'Software Lifecycle Management'
|
||||
version = '0.1'
|
||||
version = '0.96'
|
||||
author = 'Hedde van der Heide'
|
||||
author_email = 'hedde.vanderheide@ictu.nl'
|
||||
base_url = 'slm'
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from netbox.api.serializers import PrimaryModelSerializer, OrganizationalModelSerializer
|
||||
# from netbox_slm.api.nested_serializers import (
|
||||
# NestedRecordSerializer,
|
||||
# NestedNameServerSerializer,
|
||||
# )
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from netbox_slm.models import (
|
||||
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation,
|
||||
)
|
||||
|
||||
|
||||
class SoftwareProductSerializer(PrimaryModelSerializer):
|
||||
class SoftwareProductSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="plugins-api:netbox_slm-api:softwareproduct-detail"
|
||||
@@ -21,10 +19,10 @@ class SoftwareProductSerializer(PrimaryModelSerializer):
|
||||
]
|
||||
|
||||
def get_display(self, obj):
|
||||
return obj.name
|
||||
return f"{obj.manufacturer.name} - {obj.name}"
|
||||
|
||||
|
||||
class SoftwareProductVersionSerializer(PrimaryModelSerializer):
|
||||
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="plugins-api:netbox_slm-api:softwareproductversion-detail"
|
||||
@@ -38,3 +36,20 @@ class SoftwareProductVersionSerializer(PrimaryModelSerializer):
|
||||
|
||||
def get_display(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
||||
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
||||
display = serializers.SerializerMethodField()
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductInstallation
|
||||
fields = [
|
||||
'id', 'display', 'url', 'device', 'virtualmachine', 'software_product', 'version', 'tags',
|
||||
'custom_field_data', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
def get_display(self, obj):
|
||||
return obj
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from netbox.api import OrderedDefaultRouter
|
||||
from netbox.api.routers import NetBoxRouter
|
||||
from netbox_slm.api.views import (
|
||||
NetboxSLMRootView,
|
||||
SoftwareProductViewSet,
|
||||
SoftwareProductVersionViewSet,
|
||||
SoftwareProductInstallationViewSet,
|
||||
)
|
||||
|
||||
router = OrderedDefaultRouter()
|
||||
router = NetBoxRouter()
|
||||
router.APIRootView = NetboxSLMRootView
|
||||
|
||||
router.register("softwareproducts", SoftwareProductViewSet)
|
||||
router.register("softwareproductversions", SoftwareProductVersionViewSet)
|
||||
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
|
||||
urlpatterns = router.urls
|
||||
+16
-18
@@ -3,10 +3,16 @@ from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import APIRootView
|
||||
|
||||
from extras.api.views import CustomFieldModelViewSet
|
||||
from netbox_slm.api.serializers import SoftwareProductSerializer, SoftwareProductVersionSerializer
|
||||
from netbox_slm.filters import SoftwareProductFilter, SoftwareProductVersionFilter
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||
from netbox.api.viewsets import NetBoxModelViewSet
|
||||
from netbox_slm.api.serializers import (
|
||||
SoftwareProductSerializer, SoftwareProductVersionSerializer, SoftwareProductInstallationSerializer,
|
||||
)
|
||||
from netbox_slm.filters import (
|
||||
SoftwareProductFilter, SoftwareProductVersionFilter, SoftwareProductInstallationFilter,
|
||||
)
|
||||
from netbox_slm.models import (
|
||||
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation,
|
||||
)
|
||||
|
||||
|
||||
class NetboxSLMRootView(APIRootView):
|
||||
@@ -18,27 +24,19 @@ class NetboxSLMRootView(APIRootView):
|
||||
return "NetboxSLM"
|
||||
|
||||
|
||||
class SoftwareProductViewSet(CustomFieldModelViewSet):
|
||||
class SoftwareProductViewSet(NetBoxModelViewSet):
|
||||
queryset = SoftwareProduct.objects.all()
|
||||
serializer_class = SoftwareProductSerializer
|
||||
filterset_class = SoftwareProductFilter
|
||||
|
||||
# @action(detail=True, methods=["get"])
|
||||
# def records(self, request, pk=None):
|
||||
# records = Record.objects.filter(zone=pk)
|
||||
# serializer = RecordSerializer(records, many=True, context={"request": request})
|
||||
# return Response(serializer.data)
|
||||
|
||||
|
||||
class SoftwareProductVersionViewSet(CustomFieldModelViewSet):
|
||||
class SoftwareProductVersionViewSet(NetBoxModelViewSet):
|
||||
queryset = SoftwareProductVersion.objects.all()
|
||||
serializer_class = SoftwareProductVersionSerializer
|
||||
filterset_class = SoftwareProductVersionFilter
|
||||
|
||||
# @action(detail=True, methods=["get"])
|
||||
# def records(self, request, pk=None):
|
||||
# records = Record.objects.filter(zone=pk)
|
||||
# serializer = RecordSerializer(records, many=True, context={"request": request})
|
||||
# return Response(serializer.data)
|
||||
|
||||
# for reference: https://github.com/auroraresearchlab/netbox-dns/blob/main/netbox_dns/api/views.py
|
||||
class SoftwareProductInstallationViewSet(NetBoxModelViewSet):
|
||||
queryset = SoftwareProductInstallation.objects.all()
|
||||
serializer_class = SoftwareProductInstallationSerializer
|
||||
filterset_class = SoftwareProductInstallationFilter
|
||||
|
||||
@@ -2,12 +2,12 @@ import django_filters
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
from extras.filters import TagFilter
|
||||
from netbox.filtersets import PrimaryModelFilterSet
|
||||
from netbox.filtersets import NetBoxModelFilterSet
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
|
||||
from utilities.forms import DynamicModelMultipleChoiceField
|
||||
|
||||
|
||||
class BaseFilter(PrimaryModelFilterSet):
|
||||
class BaseFilter(NetBoxModelFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method="search",
|
||||
label="Search",
|
||||
|
||||
+38
-48
@@ -1,12 +1,11 @@
|
||||
from django import forms
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from extras.forms import (
|
||||
CustomFieldModelForm,
|
||||
CustomFieldModelCSVForm,
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm,
|
||||
CustomFieldModelFilterForm,
|
||||
from netbox.forms import (
|
||||
NetBoxModelForm,
|
||||
NetBoxModelCSVForm,
|
||||
NetBoxModelBulkEditForm,
|
||||
NetBoxModelFilterSetForm,
|
||||
)
|
||||
from dcim.models import Manufacturer, Device
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
|
||||
@@ -16,7 +15,7 @@ from utilities.forms import (
|
||||
from virtualization.models import VirtualMachine
|
||||
|
||||
|
||||
class SoftwareProductForm(BootstrapMixin, CustomFieldModelForm):
|
||||
class SoftwareProductForm(NetBoxModelForm):
|
||||
"""Form for creating a new SoftwareProduct object."""
|
||||
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
@@ -27,17 +26,12 @@ class SoftwareProductForm(BootstrapMixin, CustomFieldModelForm):
|
||||
# }
|
||||
)
|
||||
|
||||
# tags = DynamicModelMultipleChoiceField(
|
||||
# queryset=Tag.objects.all(),
|
||||
# required=False,
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProduct
|
||||
fields = ("name", "manufacturer", "description",) # "tags")
|
||||
fields = ("name", "manufacturer", "description", "tags")
|
||||
|
||||
|
||||
class SoftwareProductFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
|
||||
"""Form for filtering SoftwareProduct instances."""
|
||||
|
||||
model = SoftwareProduct
|
||||
@@ -52,49 +46,44 @@ class SoftwareProductFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
# tag = TagFilterField(SoftwareProduct)
|
||||
|
||||
|
||||
class SoftwareProductCSVForm(CustomFieldModelCSVForm):
|
||||
class SoftwareProductCSVForm(NetBoxModelCSVForm):
|
||||
class Meta:
|
||||
model = SoftwareProduct
|
||||
fields = ("name",)
|
||||
|
||||
|
||||
class SoftwareProductBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=SoftwareProduct.objects.all(),
|
||||
widget=forms.MultipleHiddenInput(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProduct
|
||||
nullable_fields = []
|
||||
|
||||
|
||||
class SoftwareProductVersionForm(BootstrapMixin, CustomFieldModelForm):
|
||||
class SoftwareProductVersionForm(NetBoxModelForm):
|
||||
"""Form for creating a new SoftwareProductVersion object."""
|
||||
name = forms.CharField(label=_("Version"))
|
||||
|
||||
software_product = DynamicModelChoiceField(
|
||||
queryset=SoftwareProduct.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}
|
||||
),
|
||||
)
|
||||
|
||||
# tags = DynamicModelMultipleChoiceField(
|
||||
# queryset=Tag.objects.all(),
|
||||
# required=False,
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductVersion
|
||||
fields = ("name", "software_product",) # "tags")
|
||||
fields = ("name", "software_product", "tags")
|
||||
|
||||
# def clean(self):
|
||||
# import pdb;pdb.set_trace()
|
||||
# return super(SoftwareProductVersionForm, self).clean()
|
||||
|
||||
|
||||
class SoftwareProductVersionFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
|
||||
"""Form for filtering SoftwareProductVersion instances."""
|
||||
|
||||
model = SoftwareProductVersion
|
||||
@@ -109,23 +98,24 @@ class SoftwareProductVersionFilterForm(BootstrapMixin, CustomFieldModelFilterFor
|
||||
# tag = TagFilterField(SoftwareProduct)
|
||||
|
||||
|
||||
class SoftwareProductVersionCSVForm(CustomFieldModelCSVForm):
|
||||
class SoftwareProductVersionCSVForm(NetBoxModelCSVForm):
|
||||
class Meta:
|
||||
model = SoftwareProductVersion
|
||||
fields = ("name",)
|
||||
|
||||
|
||||
class SoftwareProductVersionBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=SoftwareProduct.objects.all(),
|
||||
widget=forms.MultipleHiddenInput(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductVersion
|
||||
nullable_fields = []
|
||||
|
||||
|
||||
class SoftwareProductInstallationForm(BootstrapMixin, CustomFieldModelForm):
|
||||
class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||
"""Form for creating a new SoftwareProductInstallation object."""
|
||||
|
||||
device = DynamicModelChoiceField(
|
||||
@@ -160,23 +150,17 @@ class SoftwareProductInstallationForm(BootstrapMixin, CustomFieldModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# todo need version and device ?
|
||||
|
||||
# tags = DynamicModelMultipleChoiceField(
|
||||
# queryset=Tag.objects.all(),
|
||||
# required=False,
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
model = SoftwareProductInstallation
|
||||
fields = ("device", "virtualmachine", "software_product", "version",) # "tags")
|
||||
fields = ("device", "virtualmachine", "software_product", "version", "tags")
|
||||
|
||||
# def clean(self):
|
||||
# import pdb;pdb.set_trace()
|
||||
# return super(SoftwareProductInstallationForm, self).clean()
|
||||
def clean(self):
|
||||
if not any([self.cleaned_data['device'], self.cleaned_data['virtualmachine']]):
|
||||
raise forms.ValidationError(_("Installation requires atleast one virtualmachine or device destination."))
|
||||
return super(SoftwareProductInstallationForm, self).clean()
|
||||
|
||||
|
||||
class SoftwareProductInstallationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
||||
"""Form for filtering SoftwareProductInstallation instances."""
|
||||
|
||||
model = SoftwareProductInstallation
|
||||
@@ -186,17 +170,23 @@ class SoftwareProductInstallationFilterForm(BootstrapMixin, CustomFieldModelFilt
|
||||
# tag = TagFilterField(SoftwareProduct)
|
||||
|
||||
|
||||
class SoftwareProductInstallationCSVForm(CustomFieldModelCSVForm):
|
||||
class SoftwareProductInstallationCSVForm(NetBoxModelCSVForm):
|
||||
class Meta:
|
||||
model = SoftwareProductInstallation
|
||||
fields = tuple()
|
||||
|
||||
|
||||
class SoftwareProductInstallationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=SoftwareProductInstallation.objects.all(),
|
||||
widget=forms.MultipleHiddenInput(),
|
||||
class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
software_product = DynamicModelChoiceField(
|
||||
queryset=SoftwareProduct.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
version = DynamicModelChoiceField(
|
||||
queryset=SoftwareProductVersion.objects.all(),
|
||||
required=False
|
||||
)
|
||||
model = SoftwareProductInstallation
|
||||
fieldsets = (
|
||||
(None, ('software_product', 'version',)),
|
||||
)
|
||||
# nullable_fields = ('',)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.9 on 2022-01-24 08:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0133_port_colors'),
|
||||
('netbox_slm', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='softwareproduct',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='software_products', to='dcim.manufacturer'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 3.2.9 on 2022-04-07 12:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('netbox_slm', '0002_alter_softwareproduct_manufacturer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='softwareproduct',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='softwareproduct',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='softwareproductinstallation',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='softwareproductinstallation',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='softwareproductversion',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='softwareproductversion',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
@@ -1,13 +1,13 @@
|
||||
from django.db import models
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import safestring
|
||||
from extras.utils import extras_features
|
||||
from netbox.models import PrimaryModel
|
||||
# from extras.utils import extras_features # todo this changed in netbox 3.1.X and higher, currently not used but checkout why
|
||||
from netbox.models import NetBoxModel
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
|
||||
|
||||
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class SoftwareProduct(PrimaryModel):
|
||||
class SoftwareProduct(NetBoxModel):
|
||||
"""
|
||||
A SoftwareProduct represents ...
|
||||
"""
|
||||
@@ -17,7 +17,8 @@ class SoftwareProduct(PrimaryModel):
|
||||
manufacturer = models.ForeignKey(
|
||||
to='dcim.Manufacturer',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='software_products'
|
||||
related_name='software_products',
|
||||
null=True, blank=True
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
@@ -37,7 +38,7 @@ class SoftwareProduct(PrimaryModel):
|
||||
|
||||
|
||||
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class SoftwareProductVersion(PrimaryModel):
|
||||
class SoftwareProductVersion(NetBoxModel):
|
||||
software_product = models.ForeignKey(
|
||||
to='netbox_slm.SoftwareProduct',
|
||||
on_delete=models.PROTECT,
|
||||
@@ -62,7 +63,7 @@ class SoftwareProductVersion(PrimaryModel):
|
||||
|
||||
|
||||
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||
class SoftwareProductInstallation(PrimaryModel):
|
||||
class SoftwareProductInstallation(NetBoxModel):
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.PROTECT,
|
||||
@@ -95,3 +96,9 @@ class SoftwareProductInstallation(PrimaryModel):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("plugins:netbox_slm:softwareproductinstallation", kwargs={"pk": self.pk})
|
||||
|
||||
def get_platform(self):
|
||||
return self.device or self.virtualmachine
|
||||
|
||||
def render_type(self):
|
||||
return f"{'device' if self.device else 'virtualmachine'}"
|
||||
|
||||
+55
-26
@@ -1,10 +1,11 @@
|
||||
import django_tables2 as tables
|
||||
|
||||
from django_tables2.utils import Accessor
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
|
||||
from utilities.tables import BaseTable, ChoiceFieldColumn, ToggleColumn
|
||||
from netbox.tables import NetBoxTable, ChoiceFieldColumn, ToggleColumn, columns
|
||||
|
||||
|
||||
class SoftwareProductTable(BaseTable):
|
||||
class SoftwareProductTable(NetBoxTable):
|
||||
"""Table for displaying SoftwareProduct objects."""
|
||||
|
||||
pk = ToggleColumn()
|
||||
@@ -15,11 +16,11 @@ class SoftwareProductTable(BaseTable):
|
||||
)
|
||||
installations = tables.Column(accessor='get_installation_count', verbose_name='Installations')
|
||||
|
||||
# tags = TagColumn(
|
||||
# url_name="plugins:netbox_dns:zone_list",
|
||||
# )
|
||||
tags = columns.TagColumn(
|
||||
url_name="plugins:netbox_slm:softwareproduct_list",
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = SoftwareProduct
|
||||
fields = (
|
||||
"pk",
|
||||
@@ -27,7 +28,7 @@ class SoftwareProductTable(BaseTable):
|
||||
"manufacturer",
|
||||
"description",
|
||||
"installations",
|
||||
# "tags",
|
||||
"tags",
|
||||
)
|
||||
default_columns = (
|
||||
"pk",
|
||||
@@ -35,11 +36,17 @@ class SoftwareProductTable(BaseTable):
|
||||
"manufacturer",
|
||||
"description",
|
||||
"installations",
|
||||
# "tags",
|
||||
"tags",
|
||||
)
|
||||
sequence = (
|
||||
"manufacturer",
|
||||
"name",
|
||||
"description",
|
||||
"installations",
|
||||
)
|
||||
|
||||
|
||||
class SoftwareProductVersionTable(BaseTable):
|
||||
class SoftwareProductVersionTable(NetBoxTable):
|
||||
"""Table for displaying SoftwareProductVersion objects."""
|
||||
|
||||
pk = ToggleColumn()
|
||||
@@ -54,11 +61,11 @@ class SoftwareProductVersionTable(BaseTable):
|
||||
)
|
||||
installations = tables.Column(accessor='get_installation_count', verbose_name='Installations')
|
||||
|
||||
# tags = TagColumn(
|
||||
# url_name="plugins:netbox_dns:zone_list",
|
||||
# )
|
||||
tags = columns.TagColumn(
|
||||
url_name="plugins:netbox_slm:softwareproductversion_list",
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = SoftwareProductVersion
|
||||
fields = (
|
||||
"pk",
|
||||
@@ -66,7 +73,7 @@ class SoftwareProductVersionTable(BaseTable):
|
||||
"software_product",
|
||||
"manufacturer",
|
||||
"installations",
|
||||
# "tags",
|
||||
"tags",
|
||||
)
|
||||
default_columns = (
|
||||
"pk",
|
||||
@@ -74,11 +81,17 @@ class SoftwareProductVersionTable(BaseTable):
|
||||
"software_product",
|
||||
"manufacturer",
|
||||
"installations",
|
||||
# "tags",
|
||||
"tags",
|
||||
)
|
||||
sequence = (
|
||||
"manufacturer",
|
||||
"software_product",
|
||||
"name",
|
||||
"installations",
|
||||
)
|
||||
|
||||
|
||||
class SoftwareProductInstallationTable(BaseTable):
|
||||
class SoftwareProductInstallationTable(NetBoxTable):
|
||||
"""Table for displaying SoftwareProductInstallation objects."""
|
||||
|
||||
pk = ToggleColumn()
|
||||
@@ -91,6 +104,11 @@ class SoftwareProductInstallationTable(BaseTable):
|
||||
accessor=Accessor('virtualmachine'),
|
||||
linkify=True
|
||||
)
|
||||
platform = tables.Column(
|
||||
accessor='get_platform',
|
||||
linkify=True
|
||||
)
|
||||
type = tables.Column(accessor='render_type')
|
||||
software_product = tables.Column(
|
||||
accessor=Accessor('software_product'),
|
||||
linkify=True
|
||||
@@ -100,26 +118,37 @@ class SoftwareProductInstallationTable(BaseTable):
|
||||
linkify=True
|
||||
)
|
||||
|
||||
# tags = TagColumn(
|
||||
# url_name="plugins:netbox_dns:zone_list",
|
||||
# )
|
||||
tags = columns.TagColumn(
|
||||
url_name="plugins:netbox_slm:softwareproductinstallation_list",
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = SoftwareProductInstallation
|
||||
fields = (
|
||||
"pk",
|
||||
"name",
|
||||
"device",
|
||||
"virtualmachine",
|
||||
"platform",
|
||||
"type",
|
||||
"software_product",
|
||||
"version",
|
||||
# "tags",
|
||||
"tags",
|
||||
)
|
||||
default_columns = (
|
||||
"pk",
|
||||
"device",
|
||||
"virtualmachine",
|
||||
"platform",
|
||||
"type",
|
||||
"software_product",
|
||||
"version",
|
||||
# "tags",
|
||||
"tags",
|
||||
)
|
||||
|
||||
def order_platform(self, queryset, is_descending):
|
||||
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
|
||||
return queryset, True
|
||||
|
||||
def order_type(self, queryset, is_descending):
|
||||
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
|
||||
return queryset, True
|
||||
|
||||
def render_software_product(self, value, **kwargs):
|
||||
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
{% extends 'base/layout.html' %}
|
||||
{% load buttons %}
|
||||
{% load custom_links %}
|
||||
{% load helpers %}
|
||||
{% load perms %}
|
||||
{% load plugins %}
|
||||
{% load view_helpers %}
|
||||
|
||||
{% block header %}
|
||||
{# Breadcrumbs #}
|
||||
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
{% block breadcrumbs %}
|
||||
<li class="breadcrumb-item"><a href="{% url object|plugin_viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>
|
||||
{% endblock %}
|
||||
</ol>
|
||||
</nav>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ object }}{% endblock %}
|
||||
|
||||
{% block subtitle %}
|
||||
<div class="object-subtitle">
|
||||
<span>Created {{ object.created|annotated_date }}</span>
|
||||
<span class="separator">·</span>
|
||||
<span>Updated <span title="{{ object.last_updated }}">{{ object.last_updated|timesince }}</span> ago</span>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{# Clone/Edit/Delete Buttons #}
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
{% plugin_buttons object %}
|
||||
|
||||
{# Extra buttons #}
|
||||
{% block extra_controls %}{% endblock %}
|
||||
|
||||
{% if object.clone_fields and request.user|can_add:object %}
|
||||
{% plugin_clone_button object %}
|
||||
{% endif %}
|
||||
{% if request.user|can_change:object %}
|
||||
{% plugin_edit_button object %}
|
||||
{% endif %}
|
||||
{% if request.user|can_delete:object %}
|
||||
{% plugin_delete_button object %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="control-group">
|
||||
{% custom_links object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock controls %}
|
||||
|
||||
{% block tabs %}
|
||||
<ul class="nav nav-tabs px-3">
|
||||
{% block tab_items %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||
</li>
|
||||
{% endblock tab_items %}
|
||||
{% if perms.extras.view_journalentry %}
|
||||
{% with journal_viewname=object|plugin_viewname:'journal' %}
|
||||
{% url journal_viewname pk=object.pk as journal_url %}
|
||||
{% if journal_url %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{{ journal_url }}" class="nav-link{% if active_tab == 'journal'%} active{% endif %}">
|
||||
Journal {% badge object.journal_entries.count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
{% with changelog_viewname=object|plugin_viewname:'changelog' %}
|
||||
{% url changelog_viewname pk=object.pk as changelog_url %}
|
||||
{% if changelog_url %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{{ changelog_url }}" class="nav-link{% if active_tab == 'changelog'%} active{% endif %}">Change Log</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock tabs %}
|
||||
|
||||
{% block content-wrapper %}
|
||||
<div class="tab-content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,141 +0,0 @@
|
||||
{% extends 'base/layout.html' %}
|
||||
{% load buttons %}
|
||||
{% load helpers %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load static %}
|
||||
{% load view_helpers %}
|
||||
|
||||
{% block controls %}
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
{% block extra_controls %}{% endblock %}
|
||||
{% if permissions.add and 'add' in action_buttons %}
|
||||
{% add_button content_type.model_class|plugin_viewname:"add" %}
|
||||
{% endif %}
|
||||
{% if permissions.add and 'import' in action_buttons %}
|
||||
{% import_button content_type.model_class|plugin_viewname:"import" %}
|
||||
{% endif %}
|
||||
{% if 'export' in action_buttons %}
|
||||
{% export_button content_type %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock controls %}
|
||||
|
||||
{% block tabs %}
|
||||
<ul class="nav nav-tabs px-3">
|
||||
{% block tab_items %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
||||
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
||||
{% badge table.page.paginator.count %}
|
||||
</button>
|
||||
</li>
|
||||
{% if filter_form %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
|
||||
Filters
|
||||
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock tab_items %}
|
||||
</ul>
|
||||
{% endblock tabs %}
|
||||
|
||||
{% block content-wrapper %}
|
||||
<div class="tab-content">
|
||||
|
||||
{# Object list #}
|
||||
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
||||
|
||||
{# Applied filters #}
|
||||
{% if filter_form %}
|
||||
{% applied_filters filter_form request.GET %}
|
||||
{% endif %}
|
||||
|
||||
{# "Select all" form #}
|
||||
{% if table.paginator.num_pages > 1 %}
|
||||
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
|
||||
<div id="select-all-box" class="d-none card noprint">
|
||||
<form method="post" class="form col-md-12">
|
||||
{% csrf_token %}
|
||||
<div class="card-body">
|
||||
<div class="float-end">
|
||||
{% if bulk_edit_url and permissions.change %}
|
||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled>
|
||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if bulk_delete_url and permissions.delete %}
|
||||
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled>
|
||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
|
||||
<label for="select-all" class="form-check-label">
|
||||
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{# Object table controls #}
|
||||
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
|
||||
|
||||
<form method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
|
||||
|
||||
{# Object table #}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
{% render_table table 'inc/table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Form buttons #}
|
||||
{% if permissions.change or permissions.delete %}
|
||||
{% with bulk_edit_url=content_type.model_class|plugin_validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|plugin_validated_viewname:"bulk_delete" %}
|
||||
<div class="noprint bulk-buttons">
|
||||
<div class="bulk-button-group">
|
||||
{% block bulk_buttons %}{% endblock %}
|
||||
{% if bulk_edit_url and permissions.change %}
|
||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if bulk_delete_url and permissions.delete %}
|
||||
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
|
||||
{# Paginator #}
|
||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||
</div>
|
||||
|
||||
{# Filter form #}
|
||||
{% if filter_form %}
|
||||
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
|
||||
{% include 'inc/filter_list.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Table config form #}
|
||||
{% table_config_form table table_name="ObjectTable" %}
|
||||
{% endblock content-wrapper %}
|
||||
@@ -1,26 +1,21 @@
|
||||
{% extends 'netbox_slm/object.html' %}
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load buttons %}
|
||||
{% load static %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load view_helpers %}
|
||||
{% load perms %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="row my-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-header">
|
||||
Software Product
|
||||
</div>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
<td>{{ object.name }}</td>
|
||||
<td>{{ object }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Versions</th>
|
||||
@@ -35,42 +30,15 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{# {% include 'inc/panels/comments.html' %}#}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div class="card">
|
||||
<h5 class="card-header">Versions</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">NAME</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for version in versions %}
|
||||
<tr>
|
||||
<td>{{ version.name|truncatechars:32 }}</td>
|
||||
<td class="noprint text-end text-nowrap">
|
||||
{% if request.user|can_change:object %}
|
||||
{% plugin_edit_button version %}
|
||||
{% endif %}
|
||||
{% if request.user|can_delete:object %}
|
||||
{% plugin_delete_button version %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,82 +1,49 @@
|
||||
{% extends 'netbox_slm/object.html' %}
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load buttons %}
|
||||
{% load static %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load view_helpers %}
|
||||
{% load perms %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="row my-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Software Product
|
||||
</div>
|
||||
<h5 class="card-header">
|
||||
Software Product Installation
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
{% if object.device %}
|
||||
<tr>
|
||||
<th scope="row">Installation</th>
|
||||
<td>{{ object }}</td>
|
||||
<th scope="row">Device</th>
|
||||
<td>{{ object.device }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<th scope="row">Virtualmachine</th>
|
||||
<td>{{ object.virtualmachine }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">Software Product</th>
|
||||
<td>{{ object.software_product }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">bla</th>
|
||||
<td>
|
||||
<!-- {% for version in object.softwareproduct_versions.all %}-->
|
||||
<!-- <a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}">-->
|
||||
<!-- <span class="badge" style="color: #ffffff; background-color: #9e9e9e">{{ version }}</span>-->
|
||||
<!-- </a>-->
|
||||
<!-- {% endfor %}-->
|
||||
</td>
|
||||
<th scope="row">Version</th>
|
||||
<td>{{ object.version }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{# {% include 'inc/panels/comments.html' %}#}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="row">-->
|
||||
<!-- <div class="col col-md-12">-->
|
||||
<!-- <div class="card">-->
|
||||
<!-- <h5 class="card-header">Records</h5>-->
|
||||
<!-- <div class="card-body">-->
|
||||
<!-- <table class="table table-hover attr-table table-striped">-->
|
||||
<!-- <thead>-->
|
||||
<!-- <tr>-->
|
||||
<!-- <th scope="col">TYPE</th>-->
|
||||
<!-- <th scope="col">NAME</th>-->
|
||||
<!-- <th scope="col">VALUE</th>-->
|
||||
<!-- <th scope="col">TTL</th>-->
|
||||
<!-- <th scope="col">Actions</th>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </thead>-->
|
||||
<!-- <tbody>-->
|
||||
<!-- {% for record in records %}-->
|
||||
<!-- <tr>-->
|
||||
<!-- <td>{{ record.type }}</td>-->
|
||||
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
|
||||
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
|
||||
<!-- <td>{{ record.ttl }}</td>-->
|
||||
<!-- <td class="noprint text-end text-nowrap">-->
|
||||
<!-- {% if request.user|can_change:object %}-->
|
||||
<!-- {% plugin_edit_button record %}-->
|
||||
<!-- {% endif %}-->
|
||||
<!-- {% if request.user|can_delete:object %}-->
|
||||
<!-- {% plugin_delete_button record %}-->
|
||||
<!-- {% endif %}-->
|
||||
<!-- </td>-->
|
||||
<!-- </tr>-->
|
||||
<!-- {% endfor %}-->
|
||||
<!-- </tbody>-->
|
||||
<!-- </table>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,16 @@
|
||||
{% extends 'netbox_slm/object.html' %}
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load buttons %}
|
||||
{% load static %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load view_helpers %}
|
||||
{% load perms %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="row my-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Software Product
|
||||
</div>
|
||||
<h5 class="card-header">
|
||||
Software Product Version
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
@@ -25,58 +20,21 @@
|
||||
<tr>
|
||||
<th scope="row">Installations</th>
|
||||
<td>
|
||||
<!-- {% for version in object.softwareproduct_versions.all %}-->
|
||||
<!-- <a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}">-->
|
||||
<!-- <span class="badge" style="color: #ffffff; background-color: #9e9e9e">{{ version }}</span>-->
|
||||
<!-- </a>-->
|
||||
<!-- {% endfor %}-->
|
||||
{{ object.get_installation_count }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{# {% include 'inc/panels/comments.html' %}#}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="row">-->
|
||||
<!-- <div class="col col-md-12">-->
|
||||
<!-- <div class="card">-->
|
||||
<!-- <h5 class="card-header">Records</h5>-->
|
||||
<!-- <div class="card-body">-->
|
||||
<!-- <table class="table table-hover attr-table table-striped">-->
|
||||
<!-- <thead>-->
|
||||
<!-- <tr>-->
|
||||
<!-- <th scope="col">TYPE</th>-->
|
||||
<!-- <th scope="col">NAME</th>-->
|
||||
<!-- <th scope="col">VALUE</th>-->
|
||||
<!-- <th scope="col">TTL</th>-->
|
||||
<!-- <th scope="col">Actions</th>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </thead>-->
|
||||
<!-- <tbody>-->
|
||||
<!-- {% for record in records %}-->
|
||||
<!-- <tr>-->
|
||||
<!-- <td>{{ record.type }}</td>-->
|
||||
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
|
||||
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
|
||||
<!-- <td>{{ record.ttl }}</td>-->
|
||||
<!-- <td class="noprint text-end text-nowrap">-->
|
||||
<!-- {% if request.user|can_change:object %}-->
|
||||
<!-- {% plugin_edit_button record %}-->
|
||||
<!-- {% endif %}-->
|
||||
<!-- {% if request.user|can_delete:object %}-->
|
||||
<!-- {% plugin_delete_button record %}-->
|
||||
<!-- {% endif %}-->
|
||||
<!-- </td>-->
|
||||
<!-- </tr>-->
|
||||
<!-- {% endfor %}-->
|
||||
<!-- </tbody>-->
|
||||
<!-- </table>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
{% endblock %}
|
||||
@@ -1,91 +0,0 @@
|
||||
{% extends 'netbox_slm/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load view_helpers %}
|
||||
{% load perms %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{% comment %}
|
||||
<a href="{% url 'plugins:netbox_slm:record_add' %}?zone={{ object.id }}" class="btn btn-sm btn-primary" role="button">
|
||||
<i class="mdi mdi-plus-thick"></i> Add Record
|
||||
</a>
|
||||
{% endcomment %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Zone
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">Name</th>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Versions</th>
|
||||
<td>
|
||||
<ul>
|
||||
<!-- {% for nameserver in object.nameservers.all %}-->
|
||||
<!-- <li>-->
|
||||
<!-- <a href="{% url 'plugins:netbox_slm:nameserver' pk=nameserver.pk %}">-->
|
||||
<!-- {{ nameserver }}-->
|
||||
<!-- </a>-->
|
||||
<!-- </li>-->
|
||||
<!-- {% endfor %}-->
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/custom_fields_panel.html' %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="row">-->
|
||||
<!-- <div class="col col-md-12">-->
|
||||
<!-- <div class="card">-->
|
||||
<!-- <h5 class="card-header">Records</h5>-->
|
||||
<!-- <div class="card-body">-->
|
||||
<!-- <table class="table table-hover attr-table table-striped">-->
|
||||
<!-- <thead>-->
|
||||
<!-- <tr>-->
|
||||
<!-- <th scope="col">TYPE</th>-->
|
||||
<!-- <th scope="col">NAME</th>-->
|
||||
<!-- <th scope="col">VALUE</th>-->
|
||||
<!-- <th scope="col">TTL</th>-->
|
||||
<!-- <th scope="col">Actions</th>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </thead>-->
|
||||
<!-- <tbody>-->
|
||||
<!-- {% for record in records %}-->
|
||||
<!-- <tr>-->
|
||||
<!-- <td>{{ record.type }}</td>-->
|
||||
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
|
||||
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
|
||||
<!-- <td>{{ record.ttl }}</td>-->
|
||||
<!-- <td class="noprint text-end text-nowrap">-->
|
||||
<!-- {% if request.user|can_change:object %}-->
|
||||
<!-- {% plugin_edit_button record %}-->
|
||||
<!-- {% endif %}-->
|
||||
<!-- {% if request.user|can_delete:object %}-->
|
||||
<!-- {% plugin_delete_button record %}-->
|
||||
<!-- {% endif %}-->
|
||||
<!-- </td>-->
|
||||
<!-- </tr>-->
|
||||
<!-- {% endfor %}-->
|
||||
<!-- </tbody>-->
|
||||
<!-- </table>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
{% endblock %}
|
||||
@@ -1,94 +0,0 @@
|
||||
from django import template
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django import template
|
||||
|
||||
from utilities.templatetags.buttons import _get_viewname
|
||||
from utilities.utils import prepare_cloned_fields
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter()
|
||||
def plugin_validated_viewname(model, action):
|
||||
"""Return the view name for :
|
||||
* the given model and action if valid
|
||||
* or the given model and action if valid inside a plugin
|
||||
* or None if invalid.
|
||||
"""
|
||||
core_viewname = f"{model._meta.app_label}:{model._meta.model_name}_{action}"
|
||||
plugin_viewname = (
|
||||
f"plugins:{model._meta.app_label}:{model._meta.model_name}_{action}"
|
||||
)
|
||||
try:
|
||||
# Validate and return the view name. We don't return the actual URL yet because many of the templates
|
||||
# are written to pass a name to {% url %}.
|
||||
reverse(core_viewname)
|
||||
return core_viewname
|
||||
except NoReverseMatch:
|
||||
try:
|
||||
reverse(plugin_viewname)
|
||||
return plugin_viewname
|
||||
except NoReverseMatch:
|
||||
return None
|
||||
|
||||
|
||||
@register.filter()
|
||||
def plugin_viewname(model, action):
|
||||
"""
|
||||
Return the view name for the given model and action. Does not perform any validation.
|
||||
"""
|
||||
return f"plugins:{model._meta.app_label}:{model._meta.model_name}_{action}"
|
||||
|
||||
|
||||
#
|
||||
# Instance buttons
|
||||
#
|
||||
|
||||
|
||||
@register.inclusion_tag("buttons/clone.html")
|
||||
def plugin_clone_button(instance):
|
||||
viewname = _get_viewname(instance, "add")
|
||||
url = reverse(f"plugins:{viewname}")
|
||||
|
||||
# Populate cloned field values
|
||||
param_string = prepare_cloned_fields(instance)
|
||||
if param_string:
|
||||
url = f"{url}?{param_string}"
|
||||
|
||||
return {
|
||||
"url": url,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag("buttons/edit.html")
|
||||
def plugin_edit_button(instance, **kwargs):
|
||||
viewname = _get_viewname(instance, "edit")
|
||||
url = reverse(f"plugins:{viewname}", kwargs={"pk": instance.pk, **kwargs})
|
||||
|
||||
return {
|
||||
"url": url,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag("buttons/delete.html")
|
||||
def plugin_delete_button(instance, **kwargs):
|
||||
viewname = _get_viewname(instance, "delete")
|
||||
url = reverse(f"plugins:{viewname}", kwargs={"pk": instance.pk, **kwargs})
|
||||
|
||||
return {
|
||||
"url": url,
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# List buttons
|
||||
#
|
||||
|
||||
|
||||
@register.inclusion_tag("buttons/add.html")
|
||||
def plugin_add_button(url):
|
||||
url = reverse(f"plugins:{url}")
|
||||
|
||||
return {
|
||||
"add_url": url,
|
||||
}
|
||||
+27
-14
@@ -1,5 +1,5 @@
|
||||
from django.urls import path
|
||||
from extras.views import ObjectChangeLogView
|
||||
from netbox.views.generic import ObjectChangeLogView
|
||||
from netbox_slm import views
|
||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
|
||||
|
||||
@@ -9,9 +9,11 @@ urlpatterns = [
|
||||
path("software-products/add/", views.SoftwareProductEditView.as_view(), name='softwareproduct_add'),
|
||||
path("software-products/import/", views.SoftwareProductBulkImportView.as_view(), name="softwareproduct_import"),
|
||||
path("software-products/edit/", views.SoftwareProductBulkEditView.as_view(), name="softwareproduct_bulk_edit"),
|
||||
path("software-products/delete/", views.SoftwareProductBulkDeleteView.as_view(), name="softwareproduct_bulk_delete"),
|
||||
path("software-products/delete/", views.SoftwareProductBulkDeleteView.as_view(),
|
||||
name="softwareproduct_bulk_delete"),
|
||||
path("software-products/<int:pk>/", views.SoftwareProductView.as_view(), name="softwareproduct"),
|
||||
path("software-products/<int:pk>/delete/", views.SoftwareProductDeleteView.as_view(), name="softwareproduct_delete"),
|
||||
path("software-products/<int:pk>/delete/", views.SoftwareProductDeleteView.as_view(),
|
||||
name="softwareproduct_delete"),
|
||||
path("software-products/<int:pk>/edit/", views.SoftwareProductEditView.as_view(), name="softwareproduct_edit"),
|
||||
path(
|
||||
"software-products/<int:pk>/changelog/",
|
||||
@@ -23,11 +25,14 @@ urlpatterns = [
|
||||
# Software Product Versions
|
||||
path("versions/", views.SoftwareProductVersionListView.as_view(), name='softwareproductversion_list'),
|
||||
path("versions/add/", views.SoftwareProductVersionEditView.as_view(), name='softwareproductversion_add'),
|
||||
path("versions/import/", views.SoftwareProductVersionBulkImportView.as_view(), name="softwareproductversion_import"),
|
||||
path("versions/import/", views.SoftwareProductVersionBulkImportView.as_view(),
|
||||
name="softwareproductversion_import"),
|
||||
path("versions/edit/", views.SoftwareProductVersionBulkEditView.as_view(), name="softwareproductversion_bulk_edit"),
|
||||
path("versions/delete/", views.SoftwareProductVersionBulkDeleteView.as_view(), name="softwareproductversion_bulk_delete"),
|
||||
path("versions/delete/", views.SoftwareProductVersionBulkDeleteView.as_view(),
|
||||
name="softwareproductversion_bulk_delete"),
|
||||
path("versions/<int:pk>/", views.SoftwareProductVersionView.as_view(), name="softwareproductversion"),
|
||||
path("versions/<int:pk>/delete/", views.SoftwareProductVersionDeleteView.as_view(), name="softwareproductversion_delete"),
|
||||
path("versions/<int:pk>/delete/", views.SoftwareProductVersionDeleteView.as_view(),
|
||||
name="softwareproductversion_delete"),
|
||||
path("versions/<int:pk>/edit/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_edit"),
|
||||
path(
|
||||
"versions/<int:pk>/changelog/",
|
||||
@@ -37,14 +42,22 @@ urlpatterns = [
|
||||
),
|
||||
|
||||
# Software Product Versions
|
||||
path("installations/", views.SoftwareProductInstallationListView.as_view(), name='softwareproductinstallation_list'),
|
||||
path("installations/add/", views.SoftwareProductInstallationEditView.as_view(), name='softwareproductinstallation_add'),
|
||||
path("installations/import/", views.SoftwareProductInstallationBulkImportView.as_view(), name="softwareproductinstallation_import"),
|
||||
path("installations/edit/", views.SoftwareProductInstallationBulkEditView.as_view(), name="softwareproductinstallation_bulk_edit"),
|
||||
path("installations/delete/", views.SoftwareProductInstallationBulkDeleteView.as_view(), name="softwareproductinstallation_bulk_delete"),
|
||||
path("installations/<int:pk>/", views.SoftwareProductInstallationView.as_view(), name="softwareproductinstallation"),
|
||||
path("installations/<int:pk>/delete/", views.SoftwareProductInstallationDeleteView.as_view(), name="softwareproductinstallation_delete"),
|
||||
path("installations/<int:pk>/edit/", views.SoftwareProductInstallationEditView.as_view(), name="softwareproductinstallation_edit"),
|
||||
path("installations/", views.SoftwareProductInstallationListView.as_view(),
|
||||
name='softwareproductinstallation_list'),
|
||||
path("installations/add/", views.SoftwareProductInstallationEditView.as_view(),
|
||||
name='softwareproductinstallation_add'),
|
||||
path("installations/import/", views.SoftwareProductInstallationBulkImportView.as_view(),
|
||||
name="softwareproductinstallation_import"),
|
||||
path("installations/edit/", views.SoftwareProductInstallationBulkEditView.as_view(),
|
||||
name="softwareproductinstallation_bulk_edit"),
|
||||
path("installations/delete/", views.SoftwareProductInstallationBulkDeleteView.as_view(),
|
||||
name="softwareproductinstallation_bulk_delete"),
|
||||
path("installations/<int:pk>/", views.SoftwareProductInstallationView.as_view(),
|
||||
name="softwareproductinstallation"),
|
||||
path("installations/<int:pk>/delete/", views.SoftwareProductInstallationDeleteView.as_view(),
|
||||
name="softwareproductinstallation_delete"),
|
||||
path("installations/<int:pk>/edit/", views.SoftwareProductInstallationEditView.as_view(),
|
||||
name="softwareproductinstallation_edit"),
|
||||
path(
|
||||
"installations/<int:pk>/changelog/",
|
||||
ObjectChangeLogView.as_view(),
|
||||
|
||||
+10
-10
@@ -19,7 +19,7 @@ class SoftwareProductListView(generic.ObjectListView):
|
||||
filterset = SoftwareProductFilter
|
||||
filterset_form = SoftwareProductFilterForm
|
||||
table = SoftwareProductTable
|
||||
template_name = "netbox_slm/object_list.html"
|
||||
# template_name = "netbox_slm/object_list.html"
|
||||
|
||||
|
||||
class SoftwareProductView(generic.ObjectView):
|
||||
@@ -36,7 +36,7 @@ class SoftwareProductEditView(generic.ObjectEditView):
|
||||
"""View for editing and creating a SoftwareProduct instance."""
|
||||
|
||||
queryset = SoftwareProduct.objects.all()
|
||||
model_form = SoftwareProductForm
|
||||
form = SoftwareProductForm
|
||||
|
||||
|
||||
class SoftwareProductDeleteView(generic.ObjectDeleteView):
|
||||
@@ -70,7 +70,7 @@ class SoftwareProductVersionListView(generic.ObjectListView):
|
||||
filterset = SoftwareProductVersionFilter
|
||||
filterset_form = SoftwareProductVersionFilterForm
|
||||
table = SoftwareProductVersionTable
|
||||
template_name = "netbox_slm/object_list.html"
|
||||
# template_name = "netbox_slm/object_list.html"
|
||||
|
||||
|
||||
class SoftwareProductVersionView(generic.ObjectView):
|
||||
@@ -78,16 +78,16 @@ class SoftwareProductVersionView(generic.ObjectView):
|
||||
|
||||
queryset = SoftwareProductVersion.objects.all()
|
||||
|
||||
# def get_extra_context(self, request, instance):
|
||||
# records = instance.record_set.all()
|
||||
# return {"records": records}
|
||||
def get_extra_context(self, request, instance):
|
||||
installation_count = instance.get_installation_count()
|
||||
return {"installations": installation_count}
|
||||
|
||||
|
||||
class SoftwareProductVersionEditView(generic.ObjectEditView):
|
||||
"""View for editing and creating a SoftwareProductVersion instance."""
|
||||
|
||||
queryset = SoftwareProductVersion.objects.all()
|
||||
model_form = SoftwareProductVersionForm
|
||||
form = SoftwareProductVersionForm
|
||||
|
||||
|
||||
class SoftwareProductVersionDeleteView(generic.ObjectDeleteView):
|
||||
@@ -121,13 +121,13 @@ class SoftwareProductInstallationListView(generic.ObjectListView):
|
||||
filterset = SoftwareProductInstallationFilter
|
||||
filterset_form = SoftwareProductInstallationFilterForm
|
||||
table = SoftwareProductInstallationTable
|
||||
template_name = "netbox_slm/object_list.html"
|
||||
# template_name = "netbox_slm/object_list.html"
|
||||
|
||||
|
||||
class SoftwareProductInstallationView(generic.ObjectView):
|
||||
"""Display SoftwareProductInstallation details"""
|
||||
|
||||
queryset = SoftwareProductVersion.objects.all()
|
||||
queryset = SoftwareProductInstallation.objects.all()
|
||||
|
||||
# def get_extra_context(self, request, instance):
|
||||
# records = instance.record_set.all()
|
||||
@@ -138,7 +138,7 @@ class SoftwareProductInstallationEditView(generic.ObjectEditView):
|
||||
"""View for editing and creating a SoftwareProductInstallation instance."""
|
||||
|
||||
queryset = SoftwareProductInstallation.objects.all()
|
||||
model_form = SoftwareProductInstallationForm
|
||||
form = SoftwareProductInstallationForm
|
||||
|
||||
|
||||
class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView):
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
[metadata]
|
||||
name = netbox-slm
|
||||
version = 0.96
|
||||
description = Software Lifecycle Management Netbox Plugin.
|
||||
# long_description = file: README.rst
|
||||
# long_description_content_type='text/x-rst'
|
||||
author = Hedde van der Heide
|
||||
author_email = hedde.vanderheide@ictu.nl
|
||||
license = Apache 2.0
|
||||
classifiers =
|
||||
Environment :: Web Environment
|
||||
Framework :: Django
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Topic :: Internet :: WWW/HTTP
|
||||
Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
|
||||
[options]
|
||||
include_package_data = true
|
||||
packages = find:
|
||||
python_requires = >=3.7
|
||||
+2
-13
@@ -1,14 +1,3 @@
|
||||
from setuptools import find_packages, setup
|
||||
from setuptools import setup, Extension
|
||||
|
||||
setup(
|
||||
name='netbox-slm',
|
||||
version='0.9',
|
||||
description='Software Lifecycle Management Netbox Plugin',
|
||||
url='https://github.com/ICTU/netbox_slm',
|
||||
author='Hedde van der Heide',
|
||||
license='Apache 2.0',
|
||||
install_requires=[],
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
||||
setup()
|
||||
Reference in New Issue
Block a user