From 55ed0c6bf0decad7c514cc47b6e5a0b08e817d8e Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:50:13 +0100 Subject: [PATCH] Add new fields to models and upgrade to NetBox v3.7.2 --- .github/workflows/docker-image.yml | 4 +- CODE_OF_CONDUCT.md | 133 ++++++++++++++++++ CONTRIBUTING.md | 34 +++++ README.md | 27 ++-- SECURITY.md | 11 +- ci/Dockerfile | 2 +- ci/Dockerfile-CI | 2 +- ci/docker-compose.yml | 2 +- netbox_slm/__init__.py | 2 +- netbox_slm/api/serializers.py | 54 ++++--- netbox_slm/filtersets.py | 10 +- netbox_slm/forms/software_license.py | 23 ++- netbox_slm/forms/software_product.py | 14 +- .../forms/software_product_installation.py | 32 ++--- netbox_slm/forms/software_product_version.py | 25 +++- .../0008_software_release_types_and_more.py | 79 +++++++++++ netbox_slm/models.py | 31 +++- netbox_slm/tables.py | 12 +- .../templates/netbox_slm/softwarelicense.html | 9 ++ .../templates/netbox_slm/softwareproduct.html | 1 + .../softwareproductinstallation.html | 1 + .../netbox_slm/softwareproductversion.html | 37 ++++- 22 files changed, 456 insertions(+), 89 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 netbox_slm/migrations/0008_software_release_types_and_more.py diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 74bb16d..946ad31 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -17,8 +17,8 @@ jobs: run: docker compose build --no-cache - name: Verify the Docker image - # clearcache is provided by Django extras, which is not loaded if netbox-slm cannot be loaded - run: docker compose run netbox sh -c "/opt/netbox/venv/bin/python manage.py clearcache" + # smoke test by checking if the migrations for app netbox_slm can be displayed + run: docker compose run netbox sh -c "/opt/netbox/venv/bin/python manage.py showmigrations netbox_slm" - name: Run Django tests with coverage for netbox_slm run: | diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a28b4f1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement by +[opening a new issue in this repository's issue tracker][open issue]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations +[open issue]: https://github.com/ICTU/netbox_slm/issues/new diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..aa0662a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# NetBox SLM contributing guidelines + +## What do I need to know to help? + +If you are looking to help to with a code contribution our project uses, you can find inspiration in the [issue list](https://github.com/ICTU/netbox_slm/issues/). +Additionally, review the [README](./README.md) document and [NetBox Plugin Development](https://docs.netbox.dev/en/stable/plugins/development/) docs. + +## How do I make a contribution? + +Never made an open source contribution before? Wondering how contributions work in the in our project? Here's a quick rundown! + +1. Find an issue that you are interested in addressing or a feature that you would like to add. +2. Fork the repository associated with the issue to your local GitHub organization. This means that you will have a copy of the repository under **your-GitHub-username/repository-name**. +3. Clone the repository to your local machine using `git clone https://github.com/github-username/repository-name.git`. +4. Create a new branch for your fix using `git checkout -b branch-name-here`. +5. Make the appropriate changes for the issue you are trying to address or the feature that you want to add. +6. Use git `add insert-paths-of-changed-files-here` to add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index. +7. Use `git commit -m "Insert a short message of the changes made here"` to store the contents of the index with a descriptive message. +8. Push the changes to the remote repository using `git push origin branch-name-here`. +9. Submit a pull request to the upstream repository. +10. Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like so "Added more log outputting to resolve #4352". +11. In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainer. It's OK if your pull request is not perfect (no pull request is), the reviewer will be able to help you fix any problems and improve it! +12. Wait for the pull request to be reviewed by a maintainer. +13. Make changes to the pull request if the reviewing maintainer recommends them. +14. Celebrate your success after your pull request is merged! + +## Where can I go for help? + +If you need help, you can ask questions by [opening a new issue in this repository's issue tracker](https://github.com/ICTU/netbox_slm/issues/new). + +## What does the Code of Conduct mean for me? + +Our [Code of Conduct](./CODE_OF_CONDUCT.md) means that you are responsible for treating everyone on the project with respect and courtesy regardless of their identity. +If you are the victim of any inappropriate behavior or comments as described in our Code of Conduct, we are here for you and will do the best to ensure that the abuser is reprimanded appropriately, per our code. diff --git a/README.md b/README.md index ae8faed..6ad5e7d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -

NetBox SLM

+# NetBox SLM -

Netbox SLM is a plugin for lifecycle management of software components, including versions and installations.

+

NetBox SLM is a plugin for lifecycle management of software components, including versions and installations.

PyPi @@ -15,16 +15,16 @@ ## Known Issues -- WARNING: This plugin is only tested with a single Netbox version at this time. +- WARNING: This plugin is only tested with a single NetBox version at this time. - CSV/Bulk imports for SoftwareProduct, Version and Installation are currently broken (WIP) ## 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``. +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 +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: +to install the NetBox SLM plugin: 1. Add ``netbox_slm`` to the ``PLUGINS`` list in ``configuration/plugins.py``. @@ -59,7 +59,7 @@ to install the Netbox SLM plugin: Now, build the image: ``docker compose build --no-cache`` -And finally, run Netbox with the SLM plugin: ``docker compose up`` +And finally, run NetBox with the SLM plugin: ``docker compose up`` ## Releasing Guide @@ -84,8 +84,8 @@ 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 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. @@ -116,7 +116,7 @@ netbox docker runtime server. ] ``` -The Netbox installation above will be used to run Django management +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; @@ -128,7 +128,7 @@ explained in the next steps below; $ ./start-netbox.sh ``` -This will start Netbox locally (requires Docker) and forward the redis +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) @@ -220,3 +220,8 @@ Now you can run commands from the netbox repository like this; Visit http://127.0.0.1:8001 in the browser to see the auto reloading version of the netbox UI. Port 8000 is taken by the docker ran variant. + + +## Get in touch + +Point of contact for this repository is [Mart Visser](https://github.com/MartVisser), who can be reached by [opening a new issue in this repository's issue tracker](https://github.com/ICTU/netbox_slm/issues/new). diff --git a/SECURITY.md b/SECURITY.md index b713346..5a6ef58 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,13 +1,16 @@ -# Security Policy +# NetBox SLM Security Policy ## Current status -Netbox SLM is a plugin for Netbox with mostly default configuration. We advise to monitor Netbox security findings and update accordingly. +NetBox SLM is a plugin for [NetBox](https://github.com/netbox-community/netbox/) with mostly default configuration. +In order to stay up to date, monitor NetBox security findings and update accordingly. ## Supported Versions -Only the latest version of Netbox SLM is currently being supported with security updates. We aim to keep the plugin compatible with the latest Netbox version, but have no incentive to patch older tags at this point. +Only the latest version of NetBox SLM is currently being supported with security updates. +The intention is to keep the plugin compatible with the most recent NetBox version(s), there is no incentive to patch older tags. ## Reporting a Vulnerability -Please report security vulnerabilities by opening a ticket. The aim is to get back to you within 24 hours with a confirmation of the issue and a brief action plan or a request for more information. +You can privately [report a vulnerability issue in this repository's issue tracker](https://github.com/ICTU/netbox_slm/security/advisories/new). +The aim is to get back to you within 24 hours, with a confirmation of the issue and a brief action plan or a request for more information. diff --git a/ci/Dockerfile b/ci/Dockerfile index 0c7a21e..fd82aa7 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -11,7 +11,7 @@ RUN pip install -U .[build] RUN python -m build RUN pip install --no-index /build -FROM netboxcommunity/netbox:v3.6.1 +FROM netboxcommunity/netbox:v3.7.2 ARG PYTHON_VERSION=3.10 COPY --from=compile-image /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm diff --git a/ci/Dockerfile-CI b/ci/Dockerfile-CI index fe7e75b..7be4eeb 100644 --- a/ci/Dockerfile-CI +++ b/ci/Dockerfile-CI @@ -1,4 +1,4 @@ -FROM netboxcommunity/netbox:v3.6.1 +FROM netboxcommunity/netbox:v3.7.2 RUN mkdir /ci && chmod go+w /ci COPY ../ci/requirements_ci.txt /ci/ diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index f30dedf..67ffe55 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.7' services: netbox: &netbox - image: netboxcommunity/netbox:${VERSION-v3.6.1} + image: netboxcommunity/netbox:${VERSION-v3.7.2} depends_on: - postgres - redis diff --git a/netbox_slm/__init__.py b/netbox_slm/__init__.py index 1a3bb72..127d809 100644 --- a/netbox_slm/__init__.py +++ b/netbox_slm/__init__.py @@ -1,6 +1,6 @@ from extras.plugins import PluginConfig -__version__ = "1.5" +__version__ = "1.6.0" class SLMConfig(PluginConfig): diff --git a/netbox_slm/api/serializers.py b/netbox_slm/api/serializers.py index da53a06..7b2f508 100644 --- a/netbox_slm/api/serializers.py +++ b/netbox_slm/api/serializers.py @@ -4,47 +4,61 @@ from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense -class SoftwareProductSerializer(NetBoxModelSerializer): +class SoftwareLicenseSerializer(NetBoxModelSerializer): display = serializers.SerializerMethodField() - url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail") + url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail") class Meta: - model = SoftwareProduct + model = SoftwareLicense fields = [ "id", "display", "url", "name", + "description", + "type", + "stored_location", + "stored_location_url", + "start_date", + "expiration_date", + "support", + "license_amount", + "software_product", + "version", + "installation", "tags", + "comments", "custom_field_data", "created", "last_updated", ] def get_display(self, obj): - return f"{obj.manufacturer} - {obj}" + return f"{obj}" -class SoftwareProductVersionSerializer(NetBoxModelSerializer): +class SoftwareProductSerializer(NetBoxModelSerializer): display = serializers.SerializerMethodField() - url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail") + url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail") class Meta: - model = SoftwareProductVersion + model = SoftwareProduct fields = [ "id", "display", "url", "name", - "software_product", + "manufacturer", + "description", "tags", + "comments", "custom_field_data", "created", "last_updated", ] def get_display(self, obj): - return f"{obj}" + return f"{obj.manufacturer} - {obj}" class SoftwareProductInstallationSerializer(NetBoxModelSerializer): @@ -65,6 +79,7 @@ class Meta: "software_product", "version", "tags", + "comments", "custom_field_data", "created", "last_updated", @@ -74,26 +89,27 @@ def get_display(self, obj): return f"{obj}" -class SoftwareLicenseSerializer(NetBoxModelSerializer): +class SoftwareProductVersionSerializer(NetBoxModelSerializer): display = serializers.SerializerMethodField() - url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail") + url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail") class Meta: - model = SoftwareLicense + model = SoftwareProductVersion fields = [ "id", "display", "url", "name", - "description", - "type", - "stored_location", - "start_date", - "expiration_date", + "release_date", + "documentation_url", + "end_of_support", + "filename", + "file_checksum", + "file_link", + "release_type", "software_product", - "version", - "installation", "tags", + "comments", "custom_field_data", "created", "last_updated", diff --git a/netbox_slm/filtersets.py b/netbox_slm/filtersets.py index aee4a03..f116a2e 100644 --- a/netbox_slm/filtersets.py +++ b/netbox_slm/filtersets.py @@ -15,7 +15,12 @@ def search(self, queryset, name, value): """Perform the filtered search.""" if not value.strip(): return queryset - qs_filter = Q(name__icontains=value) | Q(manufacturer__name__icontains=value) + qs_filter = ( + Q(name__icontains=value) + | Q(description__icontains=value) + | Q(manufacturer__name__icontains=value) + | Q(comments__icontains=value) + ) return queryset.filter(qs_filter) @@ -34,6 +39,7 @@ def search(self, queryset, name, value): Q(name__icontains=value) | Q(software_product__name__icontains=value) | Q(software_product__manufacturer__name__icontains=value) + | Q(comments__icontains=value) ) return queryset.filter(qs_filter) @@ -53,6 +59,7 @@ def search(self, queryset, name, value): Q(software_product__name__icontains=value) | Q(software_product__manufacturer__name__icontains=value) | Q(version__name__icontains=value) + | Q(comments__icontains=value) ) return queryset.filter(qs_filter) @@ -75,5 +82,6 @@ def search(self, queryset, name, value): | Q(installation__device__name__icontains=value) | Q(installation__virtualmachine__name__icontains=value) | Q(installation__cluster__name__icontains=value) + | Q(comments__icontains=value) ) return queryset.filter(qs_filter) diff --git a/netbox_slm/forms/software_license.py b/netbox_slm/forms/software_license.py index 7805b9d..1c35d00 100644 --- a/netbox_slm/forms/software_license.py +++ b/netbox_slm/forms/software_license.py @@ -1,16 +1,17 @@ from django.forms import DateField from django.urls import reverse_lazy -from django.utils.translation import gettext_lazy as _ from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense -from utilities.forms.fields import DynamicModelChoiceField, TagFilterField, LaxURLField +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField from utilities.forms.widgets import APISelect, DatePicker class SoftwareLicenseForm(NetBoxModelForm): """Form for creating a new SoftwareLicense object.""" + comments = CommentField() + stored_location_url = LaxURLField(required=False) software_product = DynamicModelChoiceField( @@ -36,8 +37,8 @@ class SoftwareLicenseForm(NetBoxModelForm): "software_product": "$software_product", }, ) - start_date = DateField(label=_("Start date"), required=False, widget=DatePicker()) - expiration_date = DateField(label=_("Expiration date"), required=False, widget=DatePicker()) + start_date = DateField(required=False, widget=DatePicker()) + expiration_date = DateField(required=False, widget=DatePicker()) class Meta: model = SoftwareLicense @@ -49,25 +50,19 @@ class Meta: "stored_location_url", "start_date", "expiration_date", + "support", + "license_amount", "software_product", "version", "installation", "tags", + "comments", ) class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): model = SoftwareLicense - fieldsets = ( - ( - None, - ( - "q", - "tag", - ), - ), - ) - + fieldsets = ((None, ("q", "tag")),) tag = TagFilterField(model) diff --git a/netbox_slm/forms/software_product.py b/netbox_slm/forms/software_product.py index 086e0be..8705344 100644 --- a/netbox_slm/forms/software_product.py +++ b/netbox_slm/forms/software_product.py @@ -3,12 +3,14 @@ from dcim.models import Manufacturer from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm from netbox_slm.models import SoftwareProduct -from utilities.forms.fields import DynamicModelChoiceField, TagFilterField +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField class SoftwareProductForm(NetBoxModelForm): """Form for creating a new SoftwareProduct object.""" + comments = CommentField() + manufacturer = DynamicModelChoiceField( queryset=Manufacturer.objects.all(), required=True, @@ -16,13 +18,18 @@ class SoftwareProductForm(NetBoxModelForm): class Meta: model = SoftwareProduct - fields = ("name", "manufacturer", "description", "tags") + fields = ( + "name", + "description", + "manufacturer", + "tags", + "comments", + ) class SoftwareProductFilterForm(NetBoxModelFilterSetForm): model = SoftwareProduct fieldsets = ((None, ("q", "tag")),) - tag = TagFilterField(model) @@ -31,6 +38,7 @@ class Meta: model = SoftwareProduct fields = ( "name", + "description", "manufacturer", ) diff --git a/netbox_slm/forms/software_product_installation.py b/netbox_slm/forms/software_product_installation.py index 5e32d0a..24c2546 100644 --- a/netbox_slm/forms/software_product_installation.py +++ b/netbox_slm/forms/software_product_installation.py @@ -1,11 +1,10 @@ from django import forms from django.urls import reverse_lazy -from django.utils.translation import gettext as _ from dcim.models import Device from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion -from utilities.forms.fields import DynamicModelChoiceField, TagFilterField +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField from utilities.forms.widgets import APISelect from virtualization.models import VirtualMachine, Cluster @@ -13,6 +12,8 @@ class SoftwareProductInstallationForm(NetBoxModelForm): """Form for creating a new SoftwareProductInstallation object.""" + comments = CommentField() + device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.objects.all(), required=False) cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False) @@ -32,33 +33,30 @@ class SoftwareProductInstallationForm(NetBoxModelForm): class Meta: model = SoftwareProductInstallation - fields = ("device", "virtualmachine", "cluster", "software_product", "version", "tags") + fields = ( + "device", + "virtualmachine", + "cluster", + "software_product", + "version", + "tags", + "comments", + ) def clean_version(self): version = self.cleaned_data["version"] software_product = self.cleaned_data["software_product"] if version not in software_product.softwareproductversion_set.all(): raise forms.ValidationError( - _( - f"Version `{version}` doesn't exist on {software_product}, make sure you've " - f"selected a compatible version or first select the software product." - ) + f"Version '{version}' doesn't exist on {software_product}, make sure you've " + f"selected a compatible version or first select the software product." ) return version class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm): model = SoftwareProductInstallation - fieldsets = ( - ( - None, - ( - "q", - "tag", - ), - ), - ) - + fieldsets = ((None, ("q", "tag")),) tag = TagFilterField(model) diff --git a/netbox_slm/forms/software_product_version.py b/netbox_slm/forms/software_product_version.py index 138f5c2..34ff1f4 100644 --- a/netbox_slm/forms/software_product_version.py +++ b/netbox_slm/forms/software_product_version.py @@ -1,17 +1,19 @@ from django import forms from django.urls import reverse_lazy -from django.utils.translation import gettext as _ from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm from netbox_slm.models import SoftwareProduct, SoftwareProductVersion -from utilities.forms.fields import DynamicModelChoiceField, TagFilterField -from utilities.forms.widgets import APISelect +from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField +from utilities.forms.widgets import APISelect, DatePicker class SoftwareProductVersionForm(NetBoxModelForm): """Form for creating a new SoftwareProductVersion object.""" - name = forms.CharField(label=_("Version")) + comments = CommentField() + + release_date = forms.DateField(required=False, widget=DatePicker()) + end_of_support = forms.DateField(required=False, widget=DatePicker()) software_product = DynamicModelChoiceField( queryset=SoftwareProduct.objects.all(), @@ -20,13 +22,24 @@ class SoftwareProductVersionForm(NetBoxModelForm): class Meta: model = SoftwareProductVersion - fields = ("name", "software_product", "tags") + fields = ( + "name", + "release_date", + "documentation_url", + "end_of_support", + "filename", + "file_checksum", + "file_link", + "release_type", + "software_product", + "tags", + "comments", + ) class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): model = SoftwareProductVersion fieldsets = ((None, ("q", "tag")),) - tag = TagFilterField(model) diff --git a/netbox_slm/migrations/0008_software_release_types_and_more.py b/netbox_slm/migrations/0008_software_release_types_and_more.py new file mode 100644 index 0000000..068d7c9 --- /dev/null +++ b/netbox_slm/migrations/0008_software_release_types_and_more.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.9 on 2024-02-07 21:01 + +from django.db import migrations, models +import netbox_slm.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('netbox_slm', '0007_softwareproductinstallation_cluster'), + ] + + operations = [ + migrations.AddField( + model_name='softwarelicense', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='softwarelicense', + name='license_amount', + field=models.PositiveIntegerField(blank=True, default=None, null=True), + ), + migrations.AddField( + model_name='softwarelicense', + name='support', + field=models.BooleanField(blank=True, default=None, null=True), + ), + migrations.AddField( + model_name='softwareproduct', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='softwareproductinstallation', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='comments', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='documentation_url', + field=netbox_slm.models.LaxURLField(blank=True, max_length=1024, null=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='end_of_support', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='file_checksum', + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='file_link', + field=netbox_slm.models.LaxURLField(blank=True, max_length=1024, null=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='filename', + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='release_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='softwareproductversion', + name='release_type', + field=models.CharField(default='S', max_length=3), + ), + ] diff --git a/netbox_slm/models.py b/netbox_slm/models.py index 47a0abd..ac0d36c 100644 --- a/netbox_slm/models.py +++ b/netbox_slm/models.py @@ -18,8 +18,9 @@ class LaxURLField(models.URLField): class SoftwareProduct(NetBoxModel): name = models.CharField(max_length=128) - description = models.CharField(max_length=255, null=True, blank=True) + comments = models.TextField(blank=True) + description = models.CharField(max_length=255, null=True, blank=True) manufacturer = models.ForeignKey(to="dcim.Manufacturer", on_delete=models.PROTECT, null=True, blank=True) objects = RestrictedQuerySet.as_manager() @@ -44,8 +45,29 @@ def get_installation_count(self): ) +class SoftwareReleaseTypes(models.TextChoices): + ALPHA = "A", "Alpha" + BETA = "B", "Beta" + RELEASE_CANDIDATE = "RC", "Release candidate" + STABLE = "S", "Stable release" + + class SoftwareProductVersion(NetBoxModel): name = models.CharField(max_length=64) + comments = models.TextField(blank=True) + + release_date = models.DateField(null=True, blank=True) + documentation_url = LaxURLField(max_length=1024, null=True, blank=True) + end_of_support = models.DateField(null=True, blank=True) + filename = models.CharField(max_length=64, null=True, blank=True) + file_checksum = models.CharField(max_length=128, null=True, blank=True) + file_link = LaxURLField(max_length=1024, null=True, blank=True) + + release_type = models.CharField( + max_length=3, + choices=SoftwareReleaseTypes.choices, + default=SoftwareReleaseTypes.STABLE, + ) software_product = models.ForeignKey( to="netbox_slm.SoftwareProduct", @@ -75,6 +97,8 @@ def get_installation_count(self): class SoftwareProductInstallation(NetBoxModel): + comments = models.TextField(blank=True) + device = models.ForeignKey(to="dcim.Device", on_delete=models.PROTECT, null=True, blank=True) virtualmachine = models.ForeignKey( to="virtualization.VirtualMachine", on_delete=models.PROTECT, null=True, blank=True @@ -118,13 +142,16 @@ def render_type(self): class SoftwareLicense(NetBoxModel): name = models.CharField(max_length=128) + comments = models.TextField(blank=True) + description = models.CharField(max_length=255, null=True, blank=True) type = models.CharField(max_length=128) - stored_location = models.CharField(max_length=255, null=True, blank=True) stored_location_url = LaxURLField(max_length=1024, null=True, blank=True) start_date = models.DateField(null=True, blank=True) expiration_date = models.DateField(null=True, blank=True) + support = models.BooleanField(default=None, null=True, blank=True) + license_amount = models.PositiveIntegerField(default=None, null=True, blank=True) software_product = models.ForeignKey(to="netbox_slm.SoftwareProduct", on_delete=models.PROTECT) version = models.ForeignKey(to="netbox_slm.SoftwareProductVersion", on_delete=models.PROTECT, null=True, blank=True) diff --git a/netbox_slm/tables.py b/netbox_slm/tables.py index e49afdc..df81a6e 100644 --- a/netbox_slm/tables.py +++ b/netbox_slm/tables.py @@ -11,7 +11,7 @@ class SoftwareProductTable(NetBoxTable): pk = ToggleColumn() name = tables.LinkColumn() manufacturer = tables.Column(accessor="manufacturer", linkify=True) - installations = tables.Column(accessor="get_installation_count", verbose_name="Installations") + installations = tables.Column(accessor="get_installation_count") tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproduct_list") @@ -51,10 +51,10 @@ class SoftwareProductVersionTable(NetBoxTable): """Table for displaying SoftwareProductVersion objects.""" pk = ToggleColumn() - name = tables.LinkColumn(verbose_name="Version") + name = tables.LinkColumn() software_product = tables.Column(accessor="software_product", linkify=True) manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True) - installations = tables.Column(accessor="get_installation_count", verbose_name="Installations") + installations = tables.Column(accessor="get_installation_count") tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductversion_list") @@ -65,6 +65,9 @@ class Meta(NetBoxTable.Meta): "name", "software_product", "manufacturer", + "release_date", + "end_of_support", + "release_type", "installations", "tags", ) @@ -73,6 +76,7 @@ class Meta(NetBoxTable.Meta): "name", "software_product", "manufacturer", + "release_date", "installations", "tags", ) @@ -172,6 +176,8 @@ class Meta(NetBoxTable.Meta): "software_product", "version", "installation", + "support", + "license_amount", "tags", ) default_columns = ( diff --git a/netbox_slm/templates/netbox_slm/softwarelicense.html b/netbox_slm/templates/netbox_slm/softwarelicense.html index ad1ab42..372c713 100644 --- a/netbox_slm/templates/netbox_slm/softwarelicense.html +++ b/netbox_slm/templates/netbox_slm/softwarelicense.html @@ -49,6 +49,14 @@
Version {{ object.version }} + + Support + {{ object.support }} + + + License amount + {{ object.license_amount }} + Installation {{ object.installation }} @@ -58,6 +66,7 @@
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/comments.html' %} {% plugin_left_page object %} diff --git a/netbox_slm/templates/netbox_slm/softwareproduct.html b/netbox_slm/templates/netbox_slm/softwareproduct.html index 55df9ec..089c25b 100644 --- a/netbox_slm/templates/netbox_slm/softwareproduct.html +++ b/netbox_slm/templates/netbox_slm/softwareproduct.html @@ -42,6 +42,7 @@
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/comments.html' %} {% plugin_left_page object %} diff --git a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html index ac652a5..4819d50 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductinstallation.html +++ b/netbox_slm/templates/netbox_slm/softwareproductinstallation.html @@ -42,6 +42,7 @@
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/comments.html' %} {% plugin_left_page object %} diff --git a/netbox_slm/templates/netbox_slm/softwareproductversion.html b/netbox_slm/templates/netbox_slm/softwareproductversion.html index 85d0f84..0e4055f 100644 --- a/netbox_slm/templates/netbox_slm/softwareproductversion.html +++ b/netbox_slm/templates/netbox_slm/softwareproductversion.html @@ -17,17 +17,48 @@
Name {{ object.name }} + + Release date + {{ object.release_date }} + + + Documentation url +{% if object.documentation_url %} + {{ object.documentation_url }} +{% else %} + {{ object.documentation_url }} +{% endif %} + + + End of support + {{ object.end_of_support }} + + + Filename +{% if object.file_link %} + {{ object.filename }} +{% else %} + {{ object.filename }} +{% endif %} + + + File checksum + {{ object.file_checksum }} + + + Release type + {{ object.get_release_type_display }} + Installations - - {{ object.get_installation_count }} - + {{ object.get_installation_count }} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/comments.html' %} {% plugin_left_page object %}