## Overview
diff --git a/development/Dockerfile b/development/Dockerfile
index b43662ef..7ec1017c 100644
--- a/development/Dockerfile
+++ b/development/Dockerfile
@@ -53,29 +53,20 @@ RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \
WORKDIR /source
COPY . /source
-# Get container's installed Nautobot version as a forced constraint
-# NAUTOBOT_VER may be a branch name and not a published release therefor we need to get the installed version
-# so pip can use it to recognize local constraints.
-RUN pip show nautobot | grep "^Version: " | sed -e 's/Version: /nautobot==/' > constraints.txt
+# Build args must be declared in each stage
+ARG NAUTOBOT_VER
+ARG PYTHON_VER
-# Use Poetry to grab dev dependencies from the lock file
-# Can be improved in Poetry 1.2 which allows `poetry install --only dev`
-#
-# We can't use the entire freeze as it takes forever to resolve with rigidly fixed non-direct dependencies,
-# especially those that are only direct to Nautobot but the container included versions slightly mismatch
-RUN poetry export -f requirements.txt --without-hashes --extras all --output poetry_freeze_base.txt
-RUN poetry export -f requirements.txt --without-hashes --extras all --with dev --output poetry_freeze_all.txt
-RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_dev.txt
-
-# Install all local project as editable, constrained on Nautobot version, to get any additional
-# direct dependencies of the app
-RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \
- pip install -c constraints.txt -e .[all]
+# Constrain the Nautobot version to NAUTOBOT_VER, fall back to installing from git branch if not available on PyPi
+# In CI, this should be done outside of the Dockerfile to prevent cross-compile build failures
+ARG CI
+RUN if [ -z "${CI+x}" ]; then \
+ INSTALLED_NAUTOBOT_VER=$(pip show nautobot | grep "^Version" | sed "s/Version: //"); \
+ poetry add --lock nautobot@${INSTALLED_NAUTOBOT_VER} --python ${PYTHON_VER} || \
+ poetry add --lock git+https://github.com/nautobot/nautobot.git#${NAUTOBOT_VER} --python ${PYTHON_VER}; fi
-# Install any dev dependencies frozen from Poetry
-# Can be improved in Poetry 1.2 which allows `poetry install --only dev`
-RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \
- pip install -c constraints.txt -r poetry_freeze_dev.txt
+# Install the app
+RUN poetry install --extras all --with dev
COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py
# !!! USE CAUTION WHEN MODIFYING LINES ABOVE
diff --git a/development/app_config_schema.py b/development/app_config_schema.py
index 47009954..a779b14e 100644
--- a/development/app_config_schema.py
+++ b/development/app_config_schema.py
@@ -1,4 +1,5 @@
"""App Config Schema Generator and Validator."""
+
import json
from importlib import import_module
from os import getenv
diff --git a/development/development.env b/development/development.env
index 54f0b870..abc0579b 100644
--- a/development/development.env
+++ b/development/development.env
@@ -7,6 +7,7 @@ NAUTOBOT_BANNER_TOP="Local"
NAUTOBOT_CHANGELOG_RETENTION=0
NAUTOBOT_DEBUG=True
+NAUTOBOT_LOG_DEPRECATION_WARNINGS=True
NAUTOBOT_LOG_LEVEL=DEBUG
NAUTOBOT_METRICS_ENABLED=True
NAUTOBOT_NAPALM_TIMEOUT=5
diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml
index 708feb2b..17ffe067 100644
--- a/development/docker-compose.base.yml
+++ b/development/docker-compose.base.yml
@@ -13,7 +13,6 @@ x-nautobot-base: &nautobot-base
- "creds.env"
tty: true
-version: "3.8"
services:
nautobot:
depends_on:
diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml
index 3cae8785..9752abb1 100644
--- a/development/docker-compose.dev.yml
+++ b/development/docker-compose.dev.yml
@@ -3,7 +3,6 @@
# any override will need to include these volumes to use them.
# see: https://github.com/docker/compose/issues/3729
---
-version: "3.8"
services:
nautobot:
command: "nautobot-server runserver 0.0.0.0:8080"
diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml
index 062ada94..dbe31cba 100644
--- a/development/docker-compose.mysql.yml
+++ b/development/docker-compose.mysql.yml
@@ -1,6 +1,4 @@
---
-version: "3.8"
-
services:
nautobot:
environment:
@@ -19,7 +17,6 @@ services:
db:
image: "mysql:8"
command:
- - "--default-authentication-plugin=mysql_native_password"
- "--max_connections=1000"
env_file:
- "development.env"
diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml
index 12d1de31..8d96fdba 100644
--- a/development/docker-compose.postgres.yml
+++ b/development/docker-compose.postgres.yml
@@ -1,6 +1,4 @@
---
-version: "3.8"
-
services:
nautobot:
environment:
diff --git a/development/docker-compose.redis.yml b/development/docker-compose.redis.yml
index 6da9fa01..b5e266a3 100644
--- a/development/docker-compose.redis.yml
+++ b/development/docker-compose.redis.yml
@@ -1,5 +1,4 @@
---
-version: "3.8"
services:
redis:
image: "redis:6-alpine"
diff --git a/development/nautobot_config.py b/development/nautobot_config.py
index f83c96c8..a9fdd3cd 100644
--- a/development/nautobot_config.py
+++ b/development/nautobot_config.py
@@ -1,4 +1,5 @@
"""Nautobot development configuration file."""
+
import os
import sys
@@ -9,7 +10,7 @@
# Debug
#
-DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False))
+DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", "false"))
_TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
if DEBUG and not _TESTING:
@@ -47,9 +48,10 @@
"PASSWORD": os.getenv("NAUTOBOT_DB_PASSWORD", ""), # Database password
"HOST": os.getenv("NAUTOBOT_DB_HOST", "localhost"), # Database server
"PORT": os.getenv(
- "NAUTOBOT_DB_PORT", default_db_settings[nautobot_db_engine]["NAUTOBOT_DB_PORT"]
+ "NAUTOBOT_DB_PORT",
+ default_db_settings[nautobot_db_engine]["NAUTOBOT_DB_PORT"],
), # Database port, default to postgres
- "CONN_MAX_AGE": int(os.getenv("NAUTOBOT_DB_TIMEOUT", 300)), # Database timeout
+ "CONN_MAX_AGE": int(os.getenv("NAUTOBOT_DB_TIMEOUT", "300")), # Database timeout
"ENGINE": nautobot_db_engine,
}
}
diff --git a/development/towncrier_template.j2 b/development/towncrier_template.j2
index 04c349e6..7f6eb220 100644
--- a/development/towncrier_template.j2
+++ b/development/towncrier_template.j2
@@ -1,4 +1,15 @@
+# v{{ versiondata.version.split(".")[:2] | join(".") }} Release Notes
+
+This document describes all new features and changes in the release. The format is based on [Keep a
+Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic
+Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Release Overview
+
+- Major features or milestones
+- Changes to compatibility with Nautobot and/or other apps, libraries etc.
+
{% if render_title %}
## [v{{ versiondata.version }} ({{ versiondata.date }})](https://github.com/nautobot/nautobot-app-firewall-models/releases/tag/v{{ versiondata.version}})
@@ -12,7 +23,11 @@
{% if definitions[category]['showcontent'] %}
{% for text, values in sections[section][category].items() %}
{% for item in text.split('\n') %}
+{% if values %}
- {{ values|join(', ') }} - {{ item.strip() }}
+{% else %}
+- {{ item.strip() }}
+{% endif %}
{% endfor %}
{% endfor %}
diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md
index de829dcf..04c49e7d 100644
--- a/docs/admin/compatibility_matrix.md
+++ b/docs/admin/compatibility_matrix.md
@@ -7,3 +7,4 @@
| 1.2.X | 1.4.0 | 1.5.99 |
| 2.0.X | 2.0.0 | 2.2.99 |
| 2.1.X | 2.0.0 | 2.99.99 |
+| 2.2.X | 2.0.0 | 2.99.99 |
\ No newline at end of file
diff --git a/docs/admin/release_notes/version_2.2.md b/docs/admin/release_notes/version_2.2.md
new file mode 100644
index 00000000..e364209b
--- /dev/null
+++ b/docs/admin/release_notes/version_2.2.md
@@ -0,0 +1,28 @@
+# v2.2 Release Notes
+
+This document describes all new features and changes in the release. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Release Overview
+
+This release adds support for Python 3.12.
+
+## [v2.2.0 (2024-11-05)](https://github.com/nautobot/nautobot-app-firewall-models/releases/tag/v2.2.0)
+
+### Added
+
+- [#266](https://github.com/nautobot/nautobot-app-firewall-models/issues/266) - Added Python 3.12 support.
+
+### Fixed
+
+- [#222](https://github.com/nautobot/nautobot-app-firewall-models/issues/222) - Fixed server error when navigating to Policy detail view.
+- [#233](https://github.com/nautobot/nautobot-app-firewall-models/issues/233) - Fixed name fields being optional on multiple forms.
+- [#233](https://github.com/nautobot/nautobot-app-firewall-models/issues/233) - Fixed assigned devices and assigned dynamic groups fields not marked as optional on NATPolicy and Policy.
+- [#245](https://github.com/nautobot/nautobot-app-firewall-models/issues/245) - Fixed server error when navigating to NATPolicy detail view.
+- [#245](https://github.com/nautobot/nautobot-app-firewall-models/issues/245) - Fixed server error when updating device/dynamic group weights on NATPolicy.
+- [#272](https://github.com/nautobot/nautobot-app-firewall-models/issues/272) - Fixed migrations failing when no statuses exist in the database and various other migration issues.
+- [#275](https://github.com/nautobot/nautobot-app-firewall-models/issues/275) - Fixed capirca failures with Nautobot v2.3.3 or higher.
+- [#280](https://github.com/nautobot/nautobot-app-firewall-models/issues/280) - Fixed Capirca policy html templates.
+
+### Housekeeping
+
+- [#281](https://github.com/nautobot/nautobot-app-firewall-models/issues/281) - Changed model_class_name in .cookiecutter.json to a valid model to help with drift management.
diff --git a/docs/assets/extra.css b/docs/assets/extra.css
index 1eff1192..3f3931a0 100644
--- a/docs/assets/extra.css
+++ b/docs/assets/extra.css
@@ -96,7 +96,7 @@ a.autorefs-external:hover::after {
}
-/* Customization for mkdocs-version-annotations */
+/* Customization for markdown-version-annotations */
:root {
/* Icon for "version-added" admonition: Material Design Icons "plus-box-outline" */
--md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,');
diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md
index 02086dc8..818499f7 100644
--- a/docs/dev/contributing.md
+++ b/docs/dev/contributing.md
@@ -4,7 +4,7 @@ The project is packaged with a light [development environment](dev_environment.m
The project is following Network to Code software development guidelines and is leveraging the following:
-- Python linting and formatting: `black`, `pylint`, `bandit`, `flake8`, and `ruff`.
+- Python linting and formatting: `pylint` and `ruff`.
- YAML linting is done with `yamllint`.
- Django unit test to ensure the app is working properly.
@@ -49,24 +49,14 @@ The branching policy includes the following tenets:
Nautobot Firewall Models will observe semantic versioning, as of 1.0. This may result in a quick turnaround in minor versions to keep pace with an ever-growing feature set.
+### Backporting to Older Releases
+
+If you are backporting any fixes to a prior major or minor version of this app, please open an issue, comment on an existing issue, or post in the [Network to Code Slack](https://networktocode.slack.com/) (channel `#nautobot`).
+
+We will create a `release-X.Y` branch for you to open your PR against and cut a new release once the PR is successfully merged.
+
## Release Policy
Nautobot Firewall Models has currently no intended scheduled release schedule, and will release new features in minor versions.
-When a new release, from `develop` to `main`, is created the following should happen.
-
-- A release PR is created from `develop` with:
- - Update the release notes in `docs/admin/release_notes/version_..md` file to reflect the changes.
- - Change the version from `..-beta` to `..` in `pyproject.toml`.
- - Set the PR to the `main` branch.
-- Ensure the tests for the PR pass.
-- Merge the PR.
-- Create a new tag:
- - The tag should be in the form of `v..`.
- - The title should be in the form of `v..`.
- - The description should be the changes that were added to the `version_..md` document.
-- If merged into `main`, then push from `main` to `develop`, in order to retain the merge commit created when the PR was merged
-- A post release PR is created with:
- - Change the version from `..` to `..-beta` in both `pyproject.toml` and `nautobot.__init__.__version__`.
- - Set the PR to the proper branch, `develop`.
- - Once tests pass, merge.
+The steps taken by maintainers when creating a new release are documented in the [release checklist](./release_checklist.md).
diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md
index e1aef018..0fd1133f 100644
--- a/docs/dev/dev_environment.md
+++ b/docs/dev/dev_environment.md
@@ -123,10 +123,7 @@ Each command can be executed with `invoke `. All commands support the a
#### Testing
```
- bandit Run bandit to validate basic static code security analysis.
- black Run black to check that Python files adhere to its style standards.
- flake8 Run flake8 to check that Python files adhere to its style standards.
- ruff Run ruff to validate docstring formatting adheres to NTC defined standards.
+ ruff Run ruff to perform code formatting and/or linting.
pylint Run pylint code analysis.
tests Run all tests for this app.
unittest Run Django unit tests for the app.
@@ -454,7 +451,7 @@ This is the same as running:
### Tests
-To run tests against your code, you can run all of the tests that TravisCI runs against any new PR with:
+To run tests against your code, you can run all of the tests that the CI runs against any new PR with:
```bash
➜ invoke tests
@@ -464,9 +461,6 @@ To run an individual test, you can run any or all of the following:
```bash
➜ invoke unittest
-➜ invoke bandit
-➜ invoke black
-➜ invoke flake8
➜ invoke ruff
➜ invoke pylint
```
diff --git a/docs/dev/release_checklist.md b/docs/dev/release_checklist.md
new file mode 100644
index 00000000..4c001be8
--- /dev/null
+++ b/docs/dev/release_checklist.md
@@ -0,0 +1,214 @@
+# Release Checklist
+
+This document is intended for app maintainers and outlines the steps to perform when releasing a new version of the app.
+
+!!! important
+ Before starting, make sure your **local** `develop`, `main`, and (if applicable) the current LTM branch are all up to date with upstream!
+
+ ```
+ git fetch
+ git switch develop && git pull # and repeat for main/ltm
+ ```
+
+Choose your own adventure:
+
+- LTM release? Jump [here](#ltm-releases).
+- Patch release from `develop`? Jump [here](#all-releases-from-develop).
+- Minor release? Continue with [Minor Version Bumps](#minor-version-bumps) and then [All Releases from `develop`](#all-releases-from-develop).
+
+## Minor Version Bumps
+
+### Update Requirements
+
+Every minor version release should refresh `poetry.lock`, so that it lists the most recent stable release of each package. To do this:
+
+0. Run `poetry update --dry-run` to have Poetry automatically tell you what package updates are available and the versions it would upgrade to. This requires an existing environment created from the lock file (i.e. via `poetry install`).
+1. Review each requirement's release notes for any breaking or otherwise noteworthy changes.
+2. Run `poetry update ` to update the package versions in `poetry.lock` as appropriate.
+3. If a required package requires updating to a new release not covered in the version constraints for a package as defined in `pyproject.toml`, (e.g. `Django ~3.1.7` would never install `Django >=4.0.0`), update it manually in `pyproject.toml`.
+4. Run `poetry install` to install the refreshed versions of all required packages.
+5. Run all tests (`poetry run invoke tests`) and check that the UI and API function as expected.
+
+### Update Documentation
+
+If there are any changes to the compatibility matrix (such as a bump in the minimum supported Nautobot version), update it accordingly.
+
+Commit any resulting changes from the following sections to the documentation before proceeding with the release.
+
+!!! tip
+ Fire up the documentation server in your development environment with `poetry run mkdocs serve`! This allows you to view the documentation site locally (the link is in the output of the command) and automatically rebuilds it as you make changes.
+
+### Verify the Installation and Upgrade Steps
+
+Follow the [installation instructions](../admin/install.md) to perform a new production installation of the app. If possible, also test the [upgrade process](../admin/upgrade.md) from the previous released version.
+
+The goal of this step is to walk through the entire install process *as documented* to make sure nothing there needs to be changed or updated, to catch any errors or omissions in the documentation, and to ensure that it is current with each release.
+
+---
+
+## All Releases from `develop`
+
+### Verify CI Build Status
+
+Ensure that continuous integration testing on the `develop` branch is completing successfully.
+
+### Bump the Version
+
+Update the package version using `poetry version` if necessary. This command shows the current version of the project or bumps the version of the project and writes the new version back to `pyproject.toml` if a valid bump rule is provided.
+
+The new version must be a valid semver string or a valid bump rule: `patch`, `minor`, `major`, `prepatch`, `preminor`, `premajor`, `prerelease`. Always try to use a bump rule when you can.
+
+Display the current version with no arguments:
+
+```no-highlight
+> poetry version
+nautobot-firewall-models 1.0.0-beta.2
+```
+
+Bump pre-release versions using `prerelease`:
+
+```no-highlight
+> poetry version prerelease
+Bumping version from 1.0.0-beta.2 to 1.0.0-beta.3
+```
+
+For major versions, use `major`:
+
+```no-highlight
+> poetry version major
+Bumping version from 1.0.0-beta.2 to 1.0.0
+```
+
+For patch versions, use `minor`:
+
+```no-highlight
+> poetry version minor
+Bumping version from 1.0.0 to 1.1.0
+```
+
+And lastly, for patch versions, you guessed it, use `patch`:
+
+```no-highlight
+> poetry version patch
+Bumping version from 1.1.0 to 1.1.1
+```
+
+Please see the [official Poetry documentation on `version`](https://python-poetry.org/docs/cli/#version) for more information.
+
+### Update the Changelog
+
+!!! important
+ The changelog must adhere to the [Keep a Changelog](https://keepachangelog.com/) style guide.
+
+This guide uses `1.4.2` as the new version in its examples, so change it to match the version you bumped to in the previous step! Every. single. time. you. copy/paste commands :)
+
+First, create a release branch off of `develop` (`git switch -c release-1.4.2 develop`).
+
+> You will need to have the project's poetry environment built at this stage, as the towncrier command runs **locally only**. If you don't have it, run `poetry install` first.
+
+Generate release notes with `invoke generate-release-notes --version 1.4.2` and answer `yes` to the prompt `Is it okay if I remove those files? [Y/n]:`. This will update the release notes in `docs/admin/release_notes/version_X.Y.md`, stage that file in git, and `git rm` all the fragments that have now been incorporated into the release notes.
+
+There are two possibilities:
+
+1. If you're releasing a new major or minor version, rename the `version_X.Y.md` file accordingly (e.g. rename to `docs/admin/release_notes/version_1.4.md`). Update the `Release Overview` and add this new page to the table of contents within `mkdocs.yml`.
+2. If you're releasing a patch version, copy your version's section from the `version_X.Y.md` file into the already existing `docs/admin/release_notes/version_1.4.md` file. Delete the `version_X.Y.md` file.
+
+Stage all the changes (`git add`) and check the diffs to verify all of the changes are correct (`git diff --cached`).
+
+Commit `git commit -m "Release v1.4.2"` and `git push` the staged changes.
+
+### Submit Release Pull Request
+
+Submit a pull request titled `Release v1.4.2` to merge your release branch into `main`. Copy the documented release notes into the pull request's body.
+
+!!! important
+ Do not squash merge this branch into `main`. Make sure to select `Create a merge commit` when merging in GitHub.
+
+Once CI has completed on the PR, merge it.
+
+### Create a New Release in GitHub
+
+Draft a [new release](https://github.com/nautobot/nautobot-app-firewall-models/releases/new) with the following parameters.
+
+* **Tag:** Input current version (e.g. `v1.4.2`) and select `Create new tag: v1.4.2 on publish`
+* **Target:** `main`
+* **Title:** Version and date (e.g. `v1.4.2 - 2024-04-02`)
+
+Click "Generate Release Notes" and edit the auto-generated content as follows:
+
+- Change the entries generated by GitHub to only the usernames of the contributors. e.g. `* Updated dockerfile by @nautobot_user in https://github.com/nautobot/nautobot-app-firewall-models/pull/123` -> `* @nautobot_user`.
+ - This should give you the list for the new `Contributors` section.
+ - Make sure there are no duplicated entries.
+- Replace the content of the `What's Changed` section with the description of changes from the release PR (what towncrier generated).
+- If it exists, leave the `New Contributors` list as it is.
+
+The release notes should look as follows:
+
+```markdown
+## What's Changed
+
+**Towncrier generated Changed/Fixed/Housekeeping etc. sections here**
+
+## Contributors
+
+* @alice
+* @bob
+
+## New Contributors
+
+* @bob
+
+**Full Changelog**: https://github.com/nautobot/nautobot-app-firewall-models/compare/v1.4.1...v1.4.2
+```
+
+Publish the release!
+
+### Create a PR from `main` back to `develop`
+
+First, sync your `main` branch with upstream changes: `git switch main && git pull`.
+
+Create a new branch from `main` called `release-1.4.2-to-develop` and use `poetry version prepatch` to bump the development version to the next release.
+
+For example, if you just released `v1.4.2`:
+
+```no-highlight
+> git switch -c release-1.4.2-to-develop main
+Switched to a new branch 'release-1.4.2-to-develop'
+
+> poetry version prepatch
+Bumping version from 1.4.2 to 1.4.3a1
+
+> git add pyproject.toml && git commit -m "Bump version"
+
+> git push
+```
+
+!!! important
+ Do not squash merge this branch into `develop`. Make sure to select `Create a merge commit` when merging in GitHub.
+
+Open a new PR from `release-1.4.2-to-develop` against `develop`, wait for CI to pass, and merge it.
+
+### Final checks
+
+At this stage, the CI should be running or finished for the `v1.4.2` tag and a package successfully published to PyPI and added into the GitHub Release. Double check that's the case.
+
+Documentation should also have been built for the tag on ReadTheDocs and if you're reading this page online, refresh it and look for the new version in the little version fly-out menu down at the bottom right of the page.
+
+All done!
+
+
+## LTM Releases
+
+For projects maintaining a Nautobot LTM compatible release, all development and release management is done through the `ltm-x.y` branch. The `x.y` relates to the LTM version of Nautobot it's compatible with, for example `1.6`.
+
+The process is similar to releasing from `develop`, but there is no need for post-release branch syncing because you'll release directly from the LTM branch:
+
+1. Make sure your `ltm-1.6` branch is passing CI.
+2. Create a release branch from the `ltm-1.6` branch: `git switch -c release-1.2.3 ltm-1.6`.
+3. Bump up the patch version `poetry version patch`. If you're backporting a feature instead of bugfixes, bump the minor version instead with `poetry version minor`.
+4. Generate the release notes: `invoke generate-release-notes --version 1.2.3`.
+5. Move the release notes from the generated `docs/admin/release_notes/version_X.Y.md` to `docs/admin/release_notes/version_1.2.md`.
+6. Add all the changes and `git commit -m "Release v1.2.3"`, then `git push`.
+7. Open a new PR against `ltm-1.6`. Once CI is passing in the PR, `Create a merge commit` (don't squash!).
+8. Create a New Release in GitHub - use the same steps documented [here](#create-a-new-release-in-github).
+9. Open a separate PR against `develop` to synchronize all LTM release changelogs into the latest version of the docs for visibility.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index d168c88f..bf10c13b 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,6 @@
-mkdocs==1.5.2
-mkdocs-material==9.1.15
-mkdocs-version-annotations==1.0.0
-mkdocstrings-python==1.5.2
-mkdocstrings==0.22.0
+mkdocs==1.6.0
+mkdocs-material==9.5.32
+markdown-version-annotations==1.0.1
+griffe==1.1.1
+mkdocstrings-python==1.10.8
+mkdocstrings==0.25.2
diff --git a/invoke.example.yml b/invoke.example.yml
index 8e0c2820..95d1e536 100644
--- a/invoke.example.yml
+++ b/invoke.example.yml
@@ -1,12 +1,15 @@
---
nautobot_firewall_models:
- project_name: "nautobot-firewall-models"
nautobot_ver: "2.0.0"
- local: false
python_ver: "3.11"
- compose_dir: "development"
- compose_files:
- - "docker-compose.base.yml"
- - "docker-compose.redis.yml"
- - "docker-compose.postgres.yml"
- - "docker-compose.dev.yml"
+ # local: false
+ # compose_dir: "/full/path/to/nautobot-app-firewall-models/development"
+
+# The following is an example of using MySQL as the database backend
+# ---
+# nautobot_firewall_models:
+# compose_files:
+# - "docker-compose.base.yml"
+# - "docker-compose.redis.yml"
+# - "docker-compose.mysql.yml"
+# - "docker-compose.dev.yml"
diff --git a/mkdocs.yml b/mkdocs.yml
index 24b5f5a2..b319906d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,6 +1,6 @@
---
dev_addr: "127.0.0.1:8001"
-edit_uri: "edit/main/nautobot-app-firewall-models/docs"
+edit_uri: "edit/main/docs"
site_dir: "nautobot_firewall_models/static/nautobot_firewall_models/docs"
site_name: "Nautobot Firewall Models Documentation"
site_url: "https://docs.nautobot.com/projects/firewall-models/en/latest/"
@@ -72,6 +72,8 @@ extra:
link: "https://twitter.com/networktocode"
name: "Network to Code Twitter"
markdown_extensions:
+ - "markdown_version_annotations":
+ admonition_tag: "???"
- "admonition"
- "toc":
permalink: true
@@ -89,7 +91,6 @@ markdown_extensions:
- "footnotes"
plugins:
- "search"
- - "mkdocs-version-annotations"
- "mkdocstrings":
default_handler: "python"
handlers:
@@ -116,6 +117,7 @@ nav:
- Compatibility Matrix: "admin/compatibility_matrix.md"
- Release Notes:
- "admin/release_notes/index.md"
+ - v2.2: "admin/release_notes/version_2.2.md"
- v2.1: "admin/release_notes/version_2.1.md"
- v2.0: "admin/release_notes/version_2.0.md"
- v1.2: "admin/release_notes/version_1.2.md"
diff --git a/nautobot_firewall_models/__init__.py b/nautobot_firewall_models/__init__.py
index e5676e33..0fd3e1d1 100644
--- a/nautobot_firewall_models/__init__.py
+++ b/nautobot_firewall_models/__init__.py
@@ -1,8 +1,10 @@
"""App declaration for nautobot_firewall_models."""
+
# Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added
from importlib import metadata
from nautobot.apps import NautobotAppConfig
+from nautobot.core.signals import nautobot_database_ready
__version__ = metadata.version(__name__)
@@ -23,13 +25,17 @@ class NautobotFirewallModelsConfig(NautobotAppConfig):
"capirca_remark_pass": True,
"capirca_os_map": {},
"allowed_status": ["Active"],
+ "default_status": "Active",
"protect_on_delete": True,
}
docs_view_name = "plugins:nautobot_firewall_models:docs"
def ready(self):
"""Register custom signals."""
- import nautobot_firewall_models.signals # noqa: F401, pylint: disable=import-outside-toplevel,unused-import
+ import nautobot_firewall_models.signals # pylint: disable=import-outside-toplevel
+
+ nautobot_database_ready.connect(nautobot_firewall_models.signals.create_configured_statuses_signal, sender=self)
+ nautobot_database_ready.connect(nautobot_firewall_models.signals.associate_statuses_signal, sender=self)
super().ready()
diff --git a/nautobot_firewall_models/api/serializers.py b/nautobot_firewall_models/api/serializers.py
index bb932076..8e86bbb1 100644
--- a/nautobot_firewall_models/api/serializers.py
+++ b/nautobot_firewall_models/api/serializers.py
@@ -1,7 +1,7 @@
"""API serializers for firewall models."""
-from rest_framework import serializers
from nautobot.apps.api import NautobotModelSerializer, ValidatedModelSerializer
+from rest_framework import serializers
from nautobot_firewall_models import models
@@ -43,9 +43,8 @@ def validate(self, data):
if vrf is not None:
if qs.filter(start_address=start_address, end_address=end_address, vrf=vrf).exists():
raise serializers.ValidationError("The fields start_address, end_address, vrf must make a unique set.")
- else:
- if qs.filter(start_address=start_address, end_address=end_address, vrf__isnull=True).exists():
- raise serializers.ValidationError("The fields start_address, end_address must make a unique set.")
+ elif qs.filter(start_address=start_address, end_address=end_address, vrf__isnull=True).exists():
+ raise serializers.ValidationError("The fields start_address, end_address must make a unique set.")
return super().validate(data)
diff --git a/nautobot_firewall_models/api/urls.py b/nautobot_firewall_models/api/urls.py
index e3eeb56e..8c18b15d 100644
--- a/nautobot_firewall_models/api/urls.py
+++ b/nautobot_firewall_models/api/urls.py
@@ -4,7 +4,6 @@
from nautobot_firewall_models.api import views
-
router = OrderedDefaultRouter()
router.register("address-object", views.AddressObjectViewSet)
router.register("address-object-group", views.AddressObjectGroupViewSet)
diff --git a/nautobot_firewall_models/choices.py b/nautobot_firewall_models/choices.py
index d78062d1..207898a8 100644
--- a/nautobot_firewall_models/choices.py
+++ b/nautobot_firewall_models/choices.py
@@ -2,7 +2,6 @@
from netutils.protocol_mapper import PROTO_NAME_TO_NUM
-
IP_PROTOCOL_CHOICES = tuple((i, i) for i in PROTO_NAME_TO_NUM.keys()) # pylint: disable=consider-iterating-dictionary
ACTION_CHOICES = (("allow", "allow"), ("deny", "deny"), ("drop", "drop"), ("remark", "remark"))
diff --git a/nautobot_firewall_models/constants.py b/nautobot_firewall_models/constants.py
index d642c106..526462c3 100644
--- a/nautobot_firewall_models/constants.py
+++ b/nautobot_firewall_models/constants.py
@@ -1,4 +1,5 @@
"""Constants file."""
+
from django.conf import settings
# This is used to map the slug of the platform in the customers environment to the expected name that Capirca is looking for
diff --git a/nautobot_firewall_models/fields.py b/nautobot_firewall_models/fields.py
index 74f1ddc5..c8c7a4a5 100644
--- a/nautobot_firewall_models/fields.py
+++ b/nautobot_firewall_models/fields.py
@@ -1,7 +1,6 @@
"""Creating fields for IPRange models."""
from django import forms
-
from nautobot.ipam.formfields import IPAddressFormField
diff --git a/nautobot_firewall_models/filters.py b/nautobot_firewall_models/filters.py
index d1f4a873..558778d7 100644
--- a/nautobot_firewall_models/filters.py
+++ b/nautobot_firewall_models/filters.py
@@ -1,13 +1,14 @@
"""Filtering for Firewall Model App."""
+
+import django_filters
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.db.models import Q
-import django_filters
from nautobot.apps.filters import (
- NautobotFilterSet,
- StatusModelFilterSetMixin,
MultiValueCharFilter,
NaturalKeyOrPKMultipleChoiceFilter,
+ NautobotFilterSet,
+ StatusModelFilterSetMixin,
)
from nautobot.dcim.models import Device
@@ -162,6 +163,7 @@ def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Construct Q filter for filterset."""
if not value.strip():
return queryset
+ # pylint: disable=unsupported-binary-operation
return queryset.filter(
Q(name__icontains=value) | Q(description__icontains=value) | Q(request_id__icontains=value)
)
@@ -180,6 +182,7 @@ def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Construct Q filter for filterset."""
if not value.strip():
return queryset
+ # pylint: disable=unsupported-binary-operation
return queryset.filter(
Q(name__icontains=value) | Q(description__icontains=value) | Q(request_id__icontains=value)
)
diff --git a/nautobot_firewall_models/forms.py b/nautobot_firewall_models/forms.py
index dd4c8104..d5b1a716 100644
--- a/nautobot_firewall_models/forms.py
+++ b/nautobot_firewall_models/forms.py
@@ -1,6 +1,12 @@
"""Forms for the Firewall app."""
from django import forms
+from nautobot.apps.forms import (
+ DynamicModelChoiceField,
+ DynamicModelMultipleChoiceField,
+ TagFilterField,
+ add_blank_choice,
+)
from nautobot.dcim.models import Device, Interface
from nautobot.extras.forms import (
CustomFieldModelCSVForm,
@@ -15,12 +21,6 @@
from nautobot.ipam.models import VRF, IPAddress, Prefix
from nautobot.tenancy.forms import TenancyFilterForm, TenancyForm
from nautobot.tenancy.models import Tenant
-from nautobot.apps.forms import (
- DynamicModelChoiceField,
- DynamicModelMultipleChoiceField,
- TagFilterField,
- add_blank_choice,
-)
from nautobot_firewall_models import choices, fields, models
@@ -538,7 +538,6 @@ class PolicyRuleFilterForm(LocalContextFilterForm, NautobotFilterForm):
class PolicyRuleForm(LocalContextModelForm, NautobotModelForm):
"""PolicyRule creation/edit form."""
- name = forms.CharField(required=False, label="Name")
tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
source_users = DynamicModelMultipleChoiceField(
queryset=models.UserObject.objects.all(), label="Source User Objects", required=False
@@ -710,7 +709,6 @@ class NATPolicyRuleForm(LocalContextModelForm, NautobotModelForm):
"""NATPolicyRule creation/edit form."""
# Metadata
- name = forms.CharField(required=False, label="Name")
tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.")
diff --git a/nautobot_firewall_models/homepage.py b/nautobot_firewall_models/homepage.py
index 43fd948f..a3d4a08c 100644
--- a/nautobot_firewall_models/homepage.py
+++ b/nautobot_firewall_models/homepage.py
@@ -1,7 +1,8 @@
"""Adds App items to homepage."""
+
from nautobot.core.apps import HomePageItem, HomePagePanel
-from nautobot_firewall_models.models import Policy, PolicyRule, CapircaPolicy, NATPolicy, NATPolicyRule
+from nautobot_firewall_models.models import CapircaPolicy, NATPolicy, NATPolicyRule, Policy, PolicyRule
layout = (
HomePagePanel(
diff --git a/nautobot_firewall_models/jobs.py b/nautobot_firewall_models/jobs.py
index caeccb18..47603ad9 100644
--- a/nautobot_firewall_models/jobs.py
+++ b/nautobot_firewall_models/jobs.py
@@ -1,11 +1,10 @@
"""Jobs to run backups, intended config, and compliance."""
-from nautobot.extras.jobs import Job, MultiObjectVar, get_task_logger
from nautobot.core.celery import register_jobs
-
from nautobot.dcim.models import Device
-from nautobot_firewall_models.models import CapircaPolicy
-from nautobot_firewall_models.models import Policy
+from nautobot.extras.jobs import Job, MultiObjectVar, get_task_logger
+
+from nautobot_firewall_models.models import CapircaPolicy, Policy
logger = get_task_logger(__name__)
diff --git a/nautobot_firewall_models/migrations/0001_initial.py b/nautobot_firewall_models/migrations/0001_initial.py
index 92af72e0..a168286c 100644
--- a/nautobot_firewall_models/migrations/0001_initial.py
+++ b/nautobot_firewall_models/migrations/0001_initial.py
@@ -1,14 +1,16 @@
# Generated by Django 3.2.13 on 2022-05-16 23:53
+import uuid
+
import django.core.serializers.json
-from django.db import migrations, models
import django.db.models.deletion
import nautobot.extras.models.mixins
import nautobot.extras.models.statuses
import nautobot.ipam.fields
-import nautobot_firewall_models.validators
import taggit.managers
-import uuid
+from django.db import migrations, models
+
+import nautobot_firewall_models.validators
class Migration(migrations.Migration):
diff --git a/nautobot_firewall_models/migrations/0002_custom_status.py b/nautobot_firewall_models/migrations/0002_custom_status.py
index 170c48f5..89553588 100644
--- a/nautobot_firewall_models/migrations/0002_custom_status.py
+++ b/nautobot_firewall_models/migrations/0002_custom_status.py
@@ -1,70 +1,77 @@
# Generated by Django 3.2.13 on 2022-04-23 23:14
import os
-from django.db import migrations
-from django.core.exceptions import ObjectDoesNotExist
import yaml
+from django.db import migrations
+
+from nautobot_firewall_models.utils import (
+ create_configured_statuses,
+ get_configured_status_names,
+ get_default_status_name,
+ get_firewall_models_with_status_field,
+)
def create_status(apps, schema_editor):
"""Initial subset of statuses."""
- statuses = ["Active", "Staged", "Decommissioned"]
- ContentType = apps.get_model("contenttypes.ContentType")
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in apps.app_configs["nautobot_firewall_models"].get_models():
- if hasattr(model, "status"):
- ct = ContentType.objects.get_for_model(model)
- status.content_types.add(ct)
+ create_configured_statuses(apps)
+
+ content_types = get_firewall_models_with_status_field(apps)
+ Status = apps.get_model("extras.Status")
+ status_names = get_configured_status_names()
+ for status in Status.objects.filter(name__in=status_names).iterator():
+ status.content_types.add(*content_types)
def reverse_create_status(apps, schema_editor):
- """Reverse adding firewall models to status content_types."""
+ """Remove firewall models from status content_types."""
- statuses = ["Active", "Staged", "Decommissioned"]
ContentType = apps.get_model("contenttypes.ContentType")
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in apps.app_configs["nautobot_firewall_models"].get_models():
- if hasattr(model, "status"):
- ct = ContentType.objects.get_for_model(model)
- status.content_types.remove(ct)
+ Status = apps.get_model("extras.Status")
+ firewall_models_content_types = ContentType.objects.filter(app_label="nautobot_firewall_models")
+ for status in Status.objects.filter(content_types__in=firewall_models_content_types).distinct().iterator():
+ status.content_types.remove(*firewall_models_content_types)
def create_default_objects(apps, schema_editor):
"""Initial subset of commonly used objects."""
- defaults = os.path.join(os.path.dirname(__file__), "services.yml")
- with open(defaults, "r") as f:
- services = yaml.safe_load(f)
- status = apps.get_model("extras.Status").objects.get(name="Active")
+ default_services_file = os.path.join(os.path.dirname(__file__), "services.yml")
+ Status = apps.get_model("extras.Status")
+ ServiceObject = apps.get_model("nautobot_firewall_models.ServiceObject")
+ default_status = Status.objects.get(name=get_default_status_name())
+
+ with open(default_services_file, "r") as f:
+ default_services = yaml.safe_load(f)
- for i in services:
- apps.get_model("nautobot_firewall_models.ServiceObject").objects.create(status=status, **i)
+ for service in default_services:
+ ServiceObject.objects.create(status=default_status, **service)
def reverse_create_default_objects(apps, schema_editor):
- """Removes commonly used objects."""
- defaults = os.path.join(os.path.dirname(__file__), "services.yml")
- with open(defaults, "r") as f:
- services = yaml.safe_load(f)
- status = apps.get_model("extras.Status").objects.get(name="Active")
+ """
+ Removes commonly used objects.
+
+ Currently skipped due to Django bug https://code.djangoproject.com/ticket/33586
+ """
+ default_services_file = os.path.join(os.path.dirname(__file__), "services.yml")
+ ServiceObject = apps.get_model("nautobot_firewall_models.ServiceObject")
+
+ with open(default_services_file, "r") as f:
+ default_services = yaml.safe_load(f)
- for i in services:
- try:
- service = apps.get_model("nautobot_firewall_models.ServiceObject").objects.get(status=status, **i)
- service.delete()
- except ObjectDoesNotExist:
- continue
+ for service in default_services:
+ ServiceObject.objects.filter(**service).delete()
class Migration(migrations.Migration):
dependencies = [
+ ("contenttypes", "0002_remove_content_type_name"),
("extras", "0033_add__optimized_indexing"),
("nautobot_firewall_models", "0001_initial"),
]
operations = [
migrations.RunPython(code=create_status, reverse_code=reverse_create_status),
- migrations.RunPython(code=create_default_objects, reverse_code=reverse_create_default_objects),
+ migrations.RunPython(code=create_default_objects, reverse_code=migrations.RunPython.noop),
]
diff --git a/nautobot_firewall_models/migrations/0003_default_status.py b/nautobot_firewall_models/migrations/0003_default_status.py
index 815aa0f4..d983b60c 100644
--- a/nautobot_firewall_models/migrations/0003_default_status.py
+++ b/nautobot_firewall_models/migrations/0003_default_status.py
@@ -1,8 +1,9 @@
# Generated by Django 3.2.13 on 2022-05-16 23:54
-from django.db import migrations
import django.db.models.deletion
import nautobot.extras.models.statuses
+from django.db import migrations
+
import nautobot_firewall_models.utils
diff --git a/nautobot_firewall_models/migrations/0005_capircapolicy.py b/nautobot_firewall_models/migrations/0005_capircapolicy.py
index 5a13a1e5..97c594e7 100644
--- a/nautobot_firewall_models/migrations/0005_capircapolicy.py
+++ b/nautobot_firewall_models/migrations/0005_capircapolicy.py
@@ -1,11 +1,12 @@
# Generated by Django 3.2.14 on 2022-07-09 22:10
+import uuid
+
import django.core.serializers.json
-from django.db import migrations, models
import django.db.models.deletion
import nautobot.extras.models.mixins
import taggit.managers
-import uuid
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/nautobot_firewall_models/migrations/0006_renaming_part1.py b/nautobot_firewall_models/migrations/0006_renaming_part1.py
index ca6e87a6..3fbed127 100644
--- a/nautobot_firewall_models/migrations/0006_renaming_part1.py
+++ b/nautobot_firewall_models/migrations/0006_renaming_part1.py
@@ -1,9 +1,10 @@
# Generated by Django 3.2.15 on 2022-08-26 18:03
-from django.db import migrations, models
-import django.db.models.deletion
import uuid
+import django.db.models.deletion
+from django.db import migrations, models
+
class Migration(migrations.Migration):
dependencies = [
diff --git a/nautobot_firewall_models/migrations/0007_renaming_part2.py b/nautobot_firewall_models/migrations/0007_renaming_part2.py
index 60699e50..2d9fcebe 100644
--- a/nautobot_firewall_models/migrations/0007_renaming_part2.py
+++ b/nautobot_firewall_models/migrations/0007_renaming_part2.py
@@ -1,4 +1,5 @@
"""Custom migration for moving index to the PolicyRule."""
+
from django.db import migrations
from django.db.models import Count
diff --git a/nautobot_firewall_models/migrations/0010_nat_policy.py b/nautobot_firewall_models/migrations/0010_nat_policy.py
index 5717b655..65e9e861 100644
--- a/nautobot_firewall_models/migrations/0010_nat_policy.py
+++ b/nautobot_firewall_models/migrations/0010_nat_policy.py
@@ -1,13 +1,15 @@
# Generated by Django 3.2.15 on 2022-09-28 11:31
+import uuid
+
import django.core.serializers.json
-from django.db import migrations, models
import django.db.models.deletion
import nautobot.extras.models.mixins
import nautobot.extras.models.statuses
-import nautobot_firewall_models.utils
import taggit.managers
-import uuid
+from django.db import migrations, models
+
+import nautobot_firewall_models.utils
class Migration(migrations.Migration):
diff --git a/nautobot_firewall_models/migrations/0011_custom_status_nat.py b/nautobot_firewall_models/migrations/0011_custom_status_nat.py
index b3aae18a..c157a82c 100644
--- a/nautobot_firewall_models/migrations/0011_custom_status_nat.py
+++ b/nautobot_firewall_models/migrations/0011_custom_status_nat.py
@@ -2,6 +2,8 @@
from django.db import migrations
+from nautobot_firewall_models.utils import create_configured_statuses, get_configured_status_names
+
def create_nat_status(apps, schema_editor):
"""Initial subset of statuses for the NAT models.
@@ -9,18 +11,15 @@ def create_nat_status(apps, schema_editor):
This was added along with 0009_nat_policy in order to associate the same set of statuses with the new NAT models
that are associated with the original set of security models.
"""
+ create_configured_statuses(apps)
- statuses = ["Active", "Staged", "Decommissioned"]
ContentType = apps.get_model("contenttypes.ContentType")
- relevant_models = [
- apps.get_model(model)
- for model in ["nautobot_firewall_models.NATPolicy", "nautobot_firewall_models.NATPolicyRule"]
- ]
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in relevant_models:
- ct = ContentType.objects.get_for_model(model)
- status.content_types.add(ct)
+ Status = apps.get_model("extras.Status")
+ relevant_models_ct = ContentType.objects.filter(
+ app_label="nautobot_firewall_models", model__in=["natpolicy", "natpolicyrule"]
+ )
+ for status in Status.objects.filter(name__in=get_configured_status_names()).iterator():
+ status.content_types.add(*relevant_models_ct)
def remove_nat_status(apps, schema_editor):
@@ -30,21 +29,18 @@ def remove_nat_status(apps, schema_editor):
that are associated with the original set of security models.
"""
- statuses = ["Active", "Staged", "Decommissioned"]
ContentType = apps.get_model("contenttypes.ContentType")
- relevant_models = [
- apps.get_model(model)
- for model in ["nautobot_firewall_models.NATPolicy", "nautobot_firewall_models.NATPolicyRule"]
- ]
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in relevant_models:
- ct = ContentType.objects.get_for_model(model)
- status.content_types.remove(ct)
+ Status = apps.get_model("extras.Status")
+ relevant_models_ct = ContentType.objects.filter(
+ app_label="nautobot_firewall_models", model__in=["natpolicy", "natpolicyrule"]
+ )
+ for status in Status.objects.filter(content_types__in=relevant_models_ct).distinct().iterator():
+ status.content_types.remove(*relevant_models_ct)
class Migration(migrations.Migration):
dependencies = [
+ ("contenttypes", "0002_remove_content_type_name"),
("extras", "0033_add__optimized_indexing"),
("nautobot_firewall_models", "0010_nat_policy"),
]
diff --git a/nautobot_firewall_models/migrations/0012_remove_status_m2m_through_models.py b/nautobot_firewall_models/migrations/0012_remove_status_m2m_through_models.py
index de1b2523..e0aa29f6 100644
--- a/nautobot_firewall_models/migrations/0012_remove_status_m2m_through_models.py
+++ b/nautobot_firewall_models/migrations/0012_remove_status_m2m_through_models.py
@@ -4,18 +4,20 @@
def remove_m2m_through_status_content_types(apps, schema_editor):
"""Remove the through model content types from the Status objects."""
- statuses = ["Active", "Staged", "Decommissioned"]
ContentType = apps.get_model("contenttypes.ContentType")
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in apps.app_configs["nautobot_firewall_models"].get_models():
- if not hasattr(model, "status"):
- ct = ContentType.objects.get_for_model(model)
- status.content_types.remove(ct)
+ Status = apps.get_model("extras.Status")
+ firewall_models_without_status_field = []
+ for model in apps.app_configs["nautobot_firewall_models"].get_models():
+ if not hasattr(model, "status"):
+ ct = ContentType.objects.get_for_model(model)
+ firewall_models_without_status_field.append(ct)
+ for status in Status.objects.filter(content_types__in=firewall_models_without_status_field).distinct().iterator():
+ status.content_types.remove(*firewall_models_without_status_field)
class Migration(migrations.Migration):
dependencies = [
+ ("contenttypes", "0002_remove_content_type_name"),
("extras", "0033_add__optimized_indexing"),
("nautobot_firewall_models", "0011_custom_status_nat"),
]
diff --git a/nautobot_firewall_models/migrations/0013_applications.py b/nautobot_firewall_models/migrations/0013_applications.py
index dcd7ed4e..4157ee69 100644
--- a/nautobot_firewall_models/migrations/0013_applications.py
+++ b/nautobot_firewall_models/migrations/0013_applications.py
@@ -1,13 +1,15 @@
# Generated by Django 3.2.15 on 2022-11-22 21:23
+import uuid
+
import django.core.serializers.json
-from django.db import migrations, models
import django.db.models.deletion
import nautobot.extras.models.mixins
import nautobot.extras.models.statuses
-import nautobot_firewall_models.utils
import taggit.managers
-import uuid
+from django.db import migrations, models
+
+import nautobot_firewall_models.utils
class Migration(migrations.Migration):
diff --git a/nautobot_firewall_models/migrations/0014_custom_status_application.py b/nautobot_firewall_models/migrations/0014_custom_status_application.py
index 83ff5c0b..9fbad8db 100644
--- a/nautobot_firewall_models/migrations/0014_custom_status_application.py
+++ b/nautobot_firewall_models/migrations/0014_custom_status_application.py
@@ -2,6 +2,8 @@
from django.db import migrations
+from nautobot_firewall_models.utils import create_configured_statuses, get_configured_status_names
+
def create_app_status(apps, schema_editor):
"""Initial subset of statuses for the Application models.
@@ -9,18 +11,15 @@ def create_app_status(apps, schema_editor):
This was added along with 0013_applications in order to associate the same set of statuses with the new Application models
that are associated with the original set of security models.
"""
+ create_configured_statuses(apps)
- statuses = ["Active", "Staged", "Decommissioned"]
ContentType = apps.get_model("contenttypes.ContentType")
- relevant_models = [
- apps.get_model(model)
- for model in ["nautobot_firewall_models.ApplicationObject", "nautobot_firewall_models.ApplicationObjectGroup"]
- ]
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in relevant_models:
- ct = ContentType.objects.get_for_model(model)
- status.content_types.add(ct)
+ Status = apps.get_model("extras.Status")
+ relevant_models_ct = ContentType.objects.filter(
+ app_label="nautobot_firewall_models", model__in=["applicationobject", "applicationobjectgroup"]
+ )
+ for status in Status.objects.filter(name__in=get_configured_status_names()).iterator():
+ status.content_types.add(*relevant_models_ct)
def remove_app_status(apps, schema_editor):
@@ -29,22 +28,18 @@ def remove_app_status(apps, schema_editor):
This was added along with 0013_applications in order to associate the same set of statuses with the new Application models
that are associated with the original set of security models.
"""
-
- statuses = ["Active", "Staged", "Decommissioned"]
ContentType = apps.get_model("contenttypes.ContentType")
- relevant_models = [
- apps.get_model(model)
- for model in ["nautobot_firewall_models.ApplicationObject", "nautobot_firewall_models.ApplicationObjectGroup"]
- ]
- for i in statuses:
- status = apps.get_model("extras.Status").objects.get(name=i)
- for model in relevant_models:
- ct = ContentType.objects.get_for_model(model)
- status.content_types.remove(ct)
+ Status = apps.get_model("extras.Status")
+ relevant_models_ct = ContentType.objects.filter(
+ app_label="nautobot_firewall_models", model__in=["applicationobject", "applicationobjectgroup"]
+ )
+ for status in Status.objects.filter(content_types__in=relevant_models_ct).distinct().iterator():
+ status.content_types.remove(*relevant_models_ct)
class Migration(migrations.Migration):
dependencies = [
+ ("contenttypes", "0002_remove_content_type_name"),
("nautobot_firewall_models", "0013_applications"),
]
diff --git a/nautobot_firewall_models/migrations/0015_alter_capircapolicy_device.py b/nautobot_firewall_models/migrations/0015_alter_capircapolicy_device.py
index e49e4c5e..f9dd82b2 100644
--- a/nautobot_firewall_models/migrations/0015_alter_capircapolicy_device.py
+++ b/nautobot_firewall_models/migrations/0015_alter_capircapolicy_device.py
@@ -1,7 +1,7 @@
# Generated by Django 3.2.15 on 2023-05-03 22:37
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/nautobot_firewall_models/migrations/0016_nautobot_v2_migrations.py b/nautobot_firewall_models/migrations/0016_nautobot_v2_migrations.py
index db9fb927..977f9fcc 100644
--- a/nautobot_firewall_models/migrations/0016_nautobot_v2_migrations.py
+++ b/nautobot_firewall_models/migrations/0016_nautobot_v2_migrations.py
@@ -1,9 +1,10 @@
# Generated by Django 3.2.20 on 2023-09-12 20:51
-from django.db import migrations, models
import django.db.models.deletion
import nautobot.core.models.fields
import nautobot.extras.models.statuses
+from django.db import migrations, models
+
import nautobot_firewall_models.utils
diff --git a/nautobot_firewall_models/migrations/0018_resolve_issues_through_tables_part2.py b/nautobot_firewall_models/migrations/0018_resolve_issues_through_tables_part2.py
index fed205b9..ebfdbd2e 100644
--- a/nautobot_firewall_models/migrations/0018_resolve_issues_through_tables_part2.py
+++ b/nautobot_firewall_models/migrations/0018_resolve_issues_through_tables_part2.py
@@ -2,7 +2,6 @@
from django.db import migrations
-
affected_models = [
{"model": "nautobot_firewall_models.AddressObjectGroup", "old": "address_objects", "new": "new_address_objects"},
{"model": "nautobot_firewall_models.FQDN", "old": "ip_addresses", "new": "new_ip_addresses"},
diff --git a/nautobot_firewall_models/migrations/0020_field_cleanups.py b/nautobot_firewall_models/migrations/0020_field_cleanups.py
index 1e88ed73..62bdf6c3 100644
--- a/nautobot_firewall_models/migrations/0020_field_cleanups.py
+++ b/nautobot_firewall_models/migrations/0020_field_cleanups.py
@@ -1,6 +1,7 @@
# Generated by Django 3.2.21 on 2023-09-28 17:07
from django.db import migrations, models
+
import nautobot_firewall_models.validators
diff --git a/nautobot_firewall_models/migrations/0021_alter_addressobject_status_and_more.py b/nautobot_firewall_models/migrations/0021_alter_addressobject_status_and_more.py
index a60af5f5..8c9828fe 100644
--- a/nautobot_firewall_models/migrations/0021_alter_addressobject_status_and_more.py
+++ b/nautobot_firewall_models/migrations/0021_alter_addressobject_status_and_more.py
@@ -1,8 +1,9 @@
# Generated by Django 4.2.14 on 2024-08-06 00:36
-from django.db import migrations
import django.db.models.deletion
import nautobot.extras.models.statuses
+from django.db import migrations
+
import nautobot_firewall_models.utils
diff --git a/nautobot_firewall_models/migrations/0022_fix_policy_natpolicy_m2ms.py b/nautobot_firewall_models/migrations/0022_fix_policy_natpolicy_m2ms.py
new file mode 100644
index 00000000..7257f65f
--- /dev/null
+++ b/nautobot_firewall_models/migrations/0022_fix_policy_natpolicy_m2ms.py
@@ -0,0 +1,54 @@
+# Generated by Django 4.2.16 on 2024-10-03 21:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("dcim", "0014_location_status_data_migration"),
+ ("extras", "0047_enforce_custom_field_slug"),
+ ("nautobot_firewall_models", "0021_alter_addressobject_status_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="natpolicy",
+ name="assigned_devices",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="nat_policies",
+ through="nautobot_firewall_models.NATPolicyDeviceM2M",
+ to="dcim.device",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="natpolicy",
+ name="assigned_dynamic_groups",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="nat_policies",
+ through="nautobot_firewall_models.NATPolicyDynamicGroupM2M",
+ to="extras.dynamicgroup",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="policy",
+ name="assigned_devices",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="firewall_policies",
+ through="nautobot_firewall_models.PolicyDeviceM2M",
+ to="dcim.device",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="policy",
+ name="assigned_dynamic_groups",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="firewall_policies",
+ through="nautobot_firewall_models.PolicyDynamicGroupM2M",
+ to="extras.dynamicgroup",
+ ),
+ ),
+ ]
diff --git a/nautobot_firewall_models/models/__init__.py b/nautobot_firewall_models/models/__init__.py
index c68d476c..6d4e0bde 100644
--- a/nautobot_firewall_models/models/__init__.py
+++ b/nautobot_firewall_models/models/__init__.py
@@ -1,9 +1,9 @@
"""Load models."""
from .address import (
+ FQDN,
AddressObject,
AddressObjectGroup,
- FQDN,
IPRange,
)
from .capirca_policy import (
@@ -11,9 +11,9 @@
)
from .nat_policy import (
NATPolicy,
- NATPolicyRule,
NATPolicyDeviceM2M,
NATPolicyDynamicGroupM2M,
+ NATPolicyRule,
)
from .security_policy import (
Policy,
diff --git a/nautobot_firewall_models/models/address.py b/nautobot_firewall_models/models/address.py
index fe008bfc..149c5ecf 100644
--- a/nautobot_firewall_models/models/address.py
+++ b/nautobot_firewall_models/models/address.py
@@ -13,7 +13,6 @@
from nautobot_firewall_models.utils import get_default_status
-
###########################
# Core Models
###########################
diff --git a/nautobot_firewall_models/models/capirca_policy.py b/nautobot_firewall_models/models/capirca_policy.py
index c48ac661..8d223590 100644
--- a/nautobot_firewall_models/models/capirca_policy.py
+++ b/nautobot_firewall_models/models/capirca_policy.py
@@ -1,4 +1,5 @@
"""Models for the Capirca Configurations."""
+
# pylint: disable=duplicate-code
import logging
diff --git a/nautobot_firewall_models/models/nat_policy.py b/nautobot_firewall_models/models/nat_policy.py
index 146a6d4e..6b1c2e95 100644
--- a/nautobot_firewall_models/models/nat_policy.py
+++ b/nautobot_firewall_models/models/nat_policy.py
@@ -8,7 +8,6 @@
from nautobot_firewall_models.utils import get_default_status, model_to_json
-
###########################
# Core Models
###########################
@@ -253,10 +252,16 @@ class NATPolicy(PrimaryModel):
to="nautobot_firewall_models.NATPolicyRule", blank=True, related_name="nat_policies"
)
assigned_devices = models.ManyToManyField(
- to="dcim.Device", through="NATPolicyDeviceM2M", related_name="nat_policies"
+ to="dcim.Device",
+ through="NATPolicyDeviceM2M",
+ related_name="nat_policies",
+ blank=True,
)
assigned_dynamic_groups = models.ManyToManyField(
- to="extras.DynamicGroup", through="NATPolicyDynamicGroupM2M", related_name="nat_policies"
+ to="extras.DynamicGroup",
+ through="NATPolicyDynamicGroupM2M",
+ related_name="nat_policies",
+ blank=True,
)
status = StatusField(
on_delete=models.PROTECT,
diff --git a/nautobot_firewall_models/models/security_policy.py b/nautobot_firewall_models/models/security_policy.py
index 19deeeb6..e5fbd711 100644
--- a/nautobot_firewall_models/models/security_policy.py
+++ b/nautobot_firewall_models/models/security_policy.py
@@ -9,7 +9,6 @@
from nautobot_firewall_models import choices
from nautobot_firewall_models.utils import get_default_status, model_to_json
-
###########################
# Core Models
###########################
@@ -184,10 +183,16 @@ class Policy(PrimaryModel):
name = models.CharField(max_length=100, unique=True)
policy_rules = models.ManyToManyField(to=PolicyRule, blank=True, related_name="policies")
assigned_devices = models.ManyToManyField(
- to="dcim.Device", through="PolicyDeviceM2M", related_name="firewall_policies"
+ to="dcim.Device",
+ through="PolicyDeviceM2M",
+ related_name="firewall_policies",
+ blank=True,
)
assigned_dynamic_groups = models.ManyToManyField(
- to="extras.DynamicGroup", through="PolicyDynamicGroupM2M", related_name="firewall_policies"
+ to="extras.DynamicGroup",
+ through="PolicyDynamicGroupM2M",
+ related_name="firewall_policies",
+ blank=True,
)
status = StatusField(
on_delete=models.PROTECT,
diff --git a/nautobot_firewall_models/models/service.py b/nautobot_firewall_models/models/service.py
index c49a82f5..e9949d6c 100644
--- a/nautobot_firewall_models/models/service.py
+++ b/nautobot_firewall_models/models/service.py
@@ -9,7 +9,6 @@
from nautobot_firewall_models import choices, validators
from nautobot_firewall_models.utils import get_default_status
-
###########################
# Core Models
###########################
diff --git a/nautobot_firewall_models/models/user.py b/nautobot_firewall_models/models/user.py
index 7041f625..12486b72 100644
--- a/nautobot_firewall_models/models/user.py
+++ b/nautobot_firewall_models/models/user.py
@@ -8,7 +8,6 @@
from nautobot_firewall_models.utils import get_default_status
-
###########################
# Core Models
###########################
diff --git a/nautobot_firewall_models/models/zone.py b/nautobot_firewall_models/models/zone.py
index 0b7cf598..84887ff1 100644
--- a/nautobot_firewall_models/models/zone.py
+++ b/nautobot_firewall_models/models/zone.py
@@ -8,7 +8,6 @@
from nautobot_firewall_models.utils import get_default_status
-
###########################
# Core Models
###########################
diff --git a/nautobot_firewall_models/signals.py b/nautobot_firewall_models/signals.py
index a9917564..96df3cc6 100644
--- a/nautobot_firewall_models/signals.py
+++ b/nautobot_firewall_models/signals.py
@@ -1,13 +1,15 @@
"""Configurable signals."""
+
from django.core.exceptions import ValidationError
-from django.dispatch import receiver
from django.db.models.signals import pre_delete
+from django.dispatch import receiver
from nautobot.dcim.models import Interface
-from nautobot.ipam.models import IPAddress, Prefix, VRF
+from nautobot.extras.models import Status
+from nautobot.ipam.models import VRF, IPAddress, Prefix
from nautobot_firewall_models import models
from nautobot_firewall_models.constants import PLUGIN_CFG
-
+from nautobot_firewall_models.utils import create_configured_statuses, get_firewall_models_with_status_field
ON_DELETE = {
IPAddress: ["fqdns", "address_objects"],
@@ -102,3 +104,15 @@ def on_delete_handler(instance, **kwargs):
for i in ON_DELETE[instance._meta.model]:
if hasattr(instance, i) and getattr(instance, i).exists():
raise ValidationError(f"{instance} is assigned to an {i} & `protect_on_delete` is enabled.")
+
+
+def create_configured_statuses_signal(sender, **kwargs): # pylint: disable=unused-argument
+ """Signal handler to create default_status and allowed_status configured in the app config."""
+ create_configured_statuses()
+
+
+def associate_statuses_signal(sender, **kwargs): # pylint: disable=unused-argument
+ """Signal handler to associate some common statuses with the firewall model content types."""
+ for status in Status.objects.filter(name__in=["Active", "Staged", "Decommissioned"]):
+ content_types = get_firewall_models_with_status_field()
+ status.content_types.add(*content_types)
diff --git a/nautobot_firewall_models/tables.py b/nautobot_firewall_models/tables.py
index 048b9f27..a3ce5563 100644
--- a/nautobot_firewall_models/tables.py
+++ b/nautobot_firewall_models/tables.py
@@ -1,8 +1,8 @@
"""Table Views for Firewall Models."""
import django_tables2 as tables
-from nautobot.extras.tables import StatusTableMixin
from nautobot.apps.tables import BaseTable, ButtonsColumn, ToggleColumn
+from nautobot.extras.tables import StatusTableMixin
from nautobot_firewall_models import models
@@ -336,7 +336,7 @@ class CapircaPolicyTable(BaseTable):
pk = ToggleColumn()
device = tables.TemplateColumn(
- template_code="""{{ record.device }} """
+ template_code="""{{ record.device }} """
)
class Meta(BaseTable.Meta):
diff --git a/nautobot_firewall_models/template_content.py b/nautobot_firewall_models/template_content.py
index f6dd2231..da3a4466 100644
--- a/nautobot_firewall_models/template_content.py
+++ b/nautobot_firewall_models/template_content.py
@@ -1,4 +1,5 @@
"""Extensions of baseline Nautobot views."""
+
from nautobot.apps.ui import TemplateExtension
from nautobot_firewall_models.models import CapircaPolicy
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/capircapolicy_details.html b/nautobot_firewall_models/templates/nautobot_firewall_models/capircapolicy_details.html
index de5614c3..ab4a031c 100644
--- a/nautobot_firewall_models/templates/nautobot_firewall_models/capircapolicy_details.html
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/capircapolicy_details.html
@@ -6,29 +6,26 @@
Capirca - {{ object.device }}
-
Capirca Configuration
-
-
{{ object.cfg }}
+
Capirca Configuration
+
{{ object.cfg }}
-
Capirca Policy
+
Capirca Policy
+
{{ object.pol }}
-
{{ object.pol }}
-
-
Capirca Networks
+
Capirca Networks
+
{{ object.net }}
-
{{ object.net }}
-
-
Capirca Services
+
Capirca Services
+
{{ object.svc }}
-
{{ object.svc }}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/nat_policy_device_weight.html b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/nat_policy_device_weight.html
index 9f6d2cde..a1065717 100644
--- a/nautobot_firewall_models/templates/nautobot_firewall_models/inc/nat_policy_device_weight.html
+++ b/nautobot_firewall_models/templates/nautobot_firewall_models/inc/nat_policy_device_weight.html
@@ -6,7 +6,7 @@
Assign NAT Policy Weight to Device