Skip to content

Merge main into feature #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Aug 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1ea9c5c
Merge pull request #159 from netboxlabs/feature
bctiemann Aug 18, 2025
79ad9c6
161 fix multi object field
arthanson Aug 19, 2025
a841e1d
161 fix multi object field
arthanson Aug 19, 2025
b5d2d03
Merge pull request #162 from netboxlabs/161-multi-object-3
bctiemann Aug 19, 2025
0f477a4
Check if unique flag raise validation error if non unique data
arthanson Aug 19, 2025
c111f86
NPL-411 raise validation error if using reserved names for fields
arthanson Aug 19, 2025
962de13
Merge pull request #163 from netboxlabs/npl-389-unique
bctiemann Aug 20, 2025
8abaefa
Merge pull request #164 from netboxlabs/NPL-411-reserved-names
bctiemann Aug 20, 2025
1f0197b
Revert "Check if unique flag raise validation error if non unique data"
bctiemann Aug 20, 2025
1bc72cd
Update docs for 0.2.0
mrmrcoleman Aug 20, 2025
1178246
Merge pull request #167 from netboxlabs/revert-163-npl-389-unique
bctiemann Aug 20, 2025
91335ba
NPL-389 fix unique check
arthanson Aug 20, 2025
636d67d
Merge pull request #168 from netboxlabs/0.2.0-docs
mrmrcoleman Aug 20, 2025
a850a9c
Merge pull request #170 from netboxlabs/npl-389-unique-3
bctiemann Aug 20, 2025
b311f7b
Bump version to 0.2.0 in pyproject.toml
bctiemann Aug 20, 2025
34fb653
174 fix delete of CO on detail page
arthanson Aug 20, 2025
3216a93
174 fix delete of CO on detail page
arthanson Aug 20, 2025
1cf0c80
Merge pull request #175 from netboxlabs/174-delete
bctiemann Aug 20, 2025
f2e2da1
Merge pull request #173 from netboxlabs/bump-pyproject-version
bctiemann Aug 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ You can find further documentation [here](https://github.com/netboxlabs/netbox-c

## Requirements

* NetBox v4.2 or later
* NetBox v4.4-beta or later

## Installation

Expand All @@ -30,11 +30,12 @@ PLUGINS = [
```
$ ./manage.py migrate
```

4. Restart NetBox
```
sudo systemctl restart netbox netbox-rq
```

## Known Limitations

The Public Preview of NetBox Custom Objects is under active development as we proceed towards the General Availability release around NetBox 4.4. The best place to look for the latest list of known limitations is the [issues](https://github.com/netboxlabs/netbox-custom-objects/issues) list on the GitHub repository. These include features like Tags, Import/Export, Bulk Edit, Text Search and Branching.
The Public Preview of NetBox Custom Objects is under active development as we proceed towards the General Availability release around NetBox 4.4. The best place to look for the latest list of known limitations is the [issues](https://github.com/netboxlabs/netbox-custom-objects/issues) list on the GitHub repository.
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

## Reporting a Vulnerability

Please send any suspected vulnerability report to [email protected]
Please send any suspected vulnerability report to [[email protected]](mailto:[email protected])
42 changes: 39 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ similar to the following:
### Custom Object Type Fields

Define the schema of the Custom Object Type by creating fields of various types, with POST requests to
`/api/plugins/custom-objects/custom-object-type-fields/`:
`/api/plugins/custom-objects/custom-object-type-fields/`, referencing the ID of the Custom Object Type you just created:

```json
{
Expand Down Expand Up @@ -166,8 +166,44 @@ The response will include the created object with its assigned ID and additional
}
```

PATCH requests can be used to update all the above objects, as well as DELETE and GET operations, using the detail
URL for each model:
### API output in the browser

As with other NetBox objects, you can also view the API output for given Custom Objects by prepending `api/` to the URL, e.g. `api/plugins/custom-objects/dhcp_scope/`

```
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"url": "http://localhost:8001/api/plugins/custom-objects/dhcp_scope/1/",
"range": {
"id": 1,
"url": "http://localhost:8001/api/ipam/ip-ranges/1/",
"display": "192.168.0.1-100/24",
"family": {
"value": 4,
"label": "IPv4"
},
"start_address": "192.168.0.1/24",
"end_address": "192.168.0.100/24",
"description": ""
}
}
]
}
```

### Other operations

`PATCH`, `DELETE` and `GET` requests can be used on all of the above, using the detail URL for each model:
- Custom Object Types: `/api/plugins/custom-objects/custom-object-types/15/`
- Custom Object Type Fields: `/api/plugins/custom-objects/custom-object-type-fields/23/`
- Custom Objects: `/api/plugins/custom-objects/<custom-object-type>/15/`
99 changes: 40 additions & 59 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
# NetBox Custom Objects

[NetBox](https://github.com/netbox-community/netbox) is the world's leading source of truth for infrastructure, featuring an extensive and complex data model. But sometimes it can be useful to extend the NetBox data model to fit specific organizational needs. This plugin introduces a new paradigm for NetBox to help overcome these challenges: custom objects.
[NetBox](https://github.com/netbox-community/netbox) is the world's leading source of truth for infrastructure, featuring an extensive data model. Sometimes it can be useful to extend the NetBox data model to fit specific organizational needs. The Custom Objects plugin introduces a new paradigm for NetBox to help overcome these challenges, allowing NetBox adminstrators to extend the NetBox data model without writing a line of code.

For additional documentation on the REST API, go [here](api.md).

## Features

* Easily create new objects in NetBox - via the GUI, the REST API or `pynetbox`

* Each custom object inherits standard NetBox model functionality like REST APIs, list views, detail views and more
> [!TIP]
> NetBox Custom Objects is still in Public Preview. If you hit any problems please check the [exiting issues](https://github.com/netboxlabs/netbox-custom-objects/issues) before creating a new one. If you're unsure, start a [discussion](https://github.com/netboxlabs/netbox-custom-objects/discussions).

* Custom Objects can include fields of all standard types, like text, decimal, integer, boolean, choicesets, and more, but can also have 1-1 and 1-many fields to core NetBox models, plugin models and other Custom Object Types you have created.
## Features

* Fields on Custom Objects can model additional behaviour like uniqueness, default values, layout hints, required fields, and more.
* Easily create new object types in NetBox - via the GUI, the REST API or `pynetbox`

* Each Custom Object Type inherits standard NetBox model functionality including:
* List views, details views, etc
* Group-able fields
* An entry in the left pane for intuitive navigation
* Create Custom Fields that point to Custom Object Types
* REST APIs
* Search
* Changelogging
* Bookmarks
* Custom Links
* Cloning
* Import/Export
* EventRules
* Notifications
* Journaling
* Tags

* Custom Object Types can include 1-1 and 1-many Custom Object Type Fields of all standard types, like text, decimal, integer, boolean, etc, and can also include fields of choiceset, core NetBox models, plugin models and other Custom Object Types you have created.

* Custom Object Type Fields can model additional behaviour like uniqueness, default values, layout hints, required fields, and more.

## Terminology

Expand All @@ -33,7 +51,7 @@ Let's walk through the above DHCP Scope example, to highlight the steps involved
2. Choose a name for your Custom Object Type. In this case we will choose `dhcp_scope`

> [!TIP]
> Give your Custom Object Types URL friendly names
> Give your Custom Object Types URL friendly names

> [!TIP]
> By default the plural name for your Custom Object Type will be its name with `s` appended. So for example, multiple `dhcp_scope` Custom Objects will be referred to as `dhcp_scopes`.
Expand All @@ -49,83 +67,46 @@ Let's walk through the above DHCP Scope example, to highlight the steps involved
> The `Primary` flag on Custom Object Type Fields is used for Custom Object naming. By default when you create a Custom Object it will be called `<Custom Object Type Name> <Custom Object ID>`. So in this example the first `dhcp_scope` we create would be called `dhcp_scope 1` and so on.
> Setting `Primary` to `true` on a Custom Object Type Field causes the value of that field to be used as the name for the Custom Object.

> [!TIP]
> Uniqueness cannot be enforced for Custom Object Type Fields of type `MultiObject` or `boolean`


2. Specify a `Name` for your field, in this case we'll choose a URL friendly value: `range`.
3. Specify the `Label` for your field. This is a human readable name that will be used in the GUI. In this case we'll choose `DHCP Range`.
4. Choose a `Type` for your field. In this case we want our `range` field to be a 1-1 reference to a built-in NetBox object type, so we choose `Object`.
5. Then we need to specify which type of built-in object our `range` field will reference. Scroll down to `Related object type` and choose `IPAM > IP Range`.
6. Then click `Create`.

> [!NOTE]
> Some behaviour on Custom Object Type Fields is still under active development during the Public Preview. Please check the outstanding [issues](https://github.com/netboxlabs/netbox-custom-objects/issues) and [discussions](https://github.com/netboxlabs/netbox-custom-objects/discussions) before creating a new one.


### Interacting with Custom Objects

Typically, NetBox admins would be responsible for thinking through modelling requirements, and creating new Custom Object Types for other users to use in their day to day work. You have now created a new `DHCP Scope` Custom Object Type, so let's look at how others would interact with them.

> [!NOTE]
> When NetBox Custom Objects reaches General Availability, it will be possible to add new Custom Object Types in the left navigation pane, like other core NetBox or Plugin objects. Until then the instructions below outline the correct approach.

#### Creating a new Custom Object

Now that you've created your `DHCP Scope` Custom Object Type, let's go ahead and create a new `DHCP Scope`.

1. On the DHCP Scope detail view, click `+ Add` in the bottom right
1. Under the NetBox Custom Objects plugin in the left side navigation you will now see `Dhcp_scopes`. Click on `+` next to your new Custom Object Type.
2. As you added a single field, called `Range` of type `IPAM > IP Range` you are prompted to specify a range. Go and ahead and select one, then click `Create`.
3. You'll now see that your new `dhcp_scope` has been added into the list view at the bottom of the `DHCP Scope` Custom Object Type page.
3. You're now taken to the detail view for your new `DHCP Scope` object.

#### Standard list views for Custom Objects

As you saw in the previous step, all Custom Objects of a given Custom Object Type are viewable at the bottom of the Custom Object Type's detail page, but you can also view standard list views:

1. On the `DHCP Scope` detail view page, right click on `Dhcp_scopes` (you can also navigate to `plugins/custom-objects/dhcp_scope/`)
2. Now you will see a standard NetBox list view of your `dhcp_scopes` with the standard options including `Configure Table`, `+ Add`, etc

> [!NOTE]
> When NetBox Custom Objects reaches General Availability, it will be possible to navigate to Custom Object list views in the left navigation pane, as with core NetBox or Plugin objects. Until then the instructions above outline the correct approach.

3. As with other NetBox objects, you can also view the API output for given Custom Objects by prepending `api/` to the URL, e.g. `api/plugins/custom-objects/dhcp_scope/`

```
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"url": "http://localhost:8001/api/plugins/custom-objects/dhcp_scope/1/",
"range": {
"id": 1,
"url": "http://localhost:8001/api/ipam/ip-ranges/1/",
"display": "192.168.0.1-100/24",
"family": {
"value": 4,
"label": "IPv4"
},
"start_address": "192.168.0.1/24",
"end_address": "192.168.0.100/24",
"description": ""
}
}
]
}
```
As with core NetBox objects, Custom Objects have their own list views. To see all your `DHCP Scopes` you can just click on your Custom Object Type in the Custom Object plugin section in the left side navigation. In the example above, click on `Custom Objects` -> `OBJECTS` -> `Dhcp_scopes`

You will now see a standard NetBox list view for your new Custom Objects with the standard options including `Configure Table`, `+ Add`, Import, Export, etc

### Deletions

#### Deleting Custom Object Types

When deleting a Custom Object Type, you are in effect, deleting an entire table in the database and should be done with caution. You will be warned about the impact of before you proceed, and this is why we suggest that only admins should be allowed to interact with Custom Objects Types.
When deleting a Custom Object Type, you are in effect, deleting an entire table in the database and this should be done with caution. You will be warned about the impact of before you proceed, and this is why we suggest that only admins should be allowed to interact with Custom Objects Types.

#### Deleting Custom Object Type Fields

When deleting a Custom Object Type Field, you are in effect, deleting an entire column in the database and should be done with caution. You will be warned about the impact of before you proceed, and this is why we suggest that only admins should be allowed to interact with Custom Objects Type Fields.
When deleting a Custom Object Type Field, you are in effect, deleting an entire column in the database and this should be done with caution. You will be warned about the impact of before you proceed, and this is why we suggest that only admins should be allowed to interact with Custom Objects Type Fields.

#### Deleting Custom Objects

Deleting Custom Objects, like a specific DHCP Scope object you have created, works just like it does for normal NetBox objects. You can delete a Custom Object on the Custom Object's detail view, or via one of the two list views. We recommend that you follow your usual permissions practices here.
Deleting Custom Objects, like a specific DHCP Scope object you have created, works just like it does for normal NetBox objects. You can delete a Custom Object on the Custom Object's detail view, or via the list view. We recommend that you follow your usual permissions practices here.
83 changes: 20 additions & 63 deletions netbox_custom_objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,6 @@ def is_running_migration():
return False


def is_in_clear_cache():
"""
Check if the code is currently being called from Django's clear_cache() method.

TODO: This is fairly ugly, but in models.CustomObjectType.get_model() we call
meta = type() which calls clear_cache on the model which causes a call to
get_models() which in-turn calls get_model and therefore recurses.

This catches the specific case of a recursive call to get_models() from
clear_cache() which is the only case we care about, so should be relatively
safe. An alternative should be found for this.
"""
import inspect

frame = inspect.currentframe()
try:
# Walk up the call stack to see if we're being called from clear_cache
while frame:
if (
frame.f_code.co_name == "clear_cache"
and "django/apps/registry.py" in frame.f_code.co_filename
):
return True
frame = frame.f_back
return False
finally:
# Clean up the frame reference
del frame


def check_custom_object_type_table_exists():
"""
Check if the CustomObjectType table exists in the database.
Expand Down Expand Up @@ -79,7 +49,6 @@ class CustomObjectsPluginConfig(PluginConfig):
default_settings = {}
required_settings = []
template_extensions = "template_content.template_extensions"
_in_get_models = False # Recursion guard

def get_model(self, model_name, require_ready=True):
try:
Expand Down Expand Up @@ -115,45 +84,33 @@ def get_model(self, model_name, require_ready=True):

def get_models(self, include_auto_created=False, include_swapped=False):
"""Return all models for this plugin, including custom object type models."""

# Get the regular Django models first
for model in super().get_models(include_auto_created, include_swapped):
yield model

# Prevent recursion
if self._in_get_models and is_in_clear_cache():
# Skip dynamic model creation if we're in a recursive get_models call
return
# Suppress warnings about database calls during model loading
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", category=RuntimeWarning, message=".*database.*"
)
warnings.filterwarnings(
"ignore", category=UserWarning, message=".*database.*"
)

self._in_get_models = True
try:
# Suppress warnings about database calls during model loading
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", category=RuntimeWarning, message=".*database.*"
)
warnings.filterwarnings(
"ignore", category=UserWarning, message=".*database.*"
)

# Skip custom object type model loading if running during migration
if (
is_running_migration()
or not check_custom_object_type_table_exists()
):
return

# Add custom object type models
from .models import CustomObjectType

custom_object_types = CustomObjectType.objects.all()
for custom_type in custom_object_types:
model = custom_type.get_model()
# Skip custom object type model loading if running during migration
if is_running_migration() or not check_custom_object_type_table_exists():
return

# Add custom object type models
from .models import CustomObjectType

custom_object_types = CustomObjectType.objects.all()
for custom_type in custom_object_types:
# Only yield already cached models during discovery
if CustomObjectType.is_model_cached(custom_type.id):
model = CustomObjectType.get_cached_model(custom_type.id)
if model:
yield model
finally:
# Clean up the recursion guard
self._in_get_models = False


config = CustomObjectsPluginConfig
16 changes: 16 additions & 0 deletions netbox_custom_objects/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,19 @@
)

APP_LABEL = "netbox_custom_objects"

# Field names that are reserved and cannot be used for custom object fields
RESERVED_FIELD_NAMES = [
"bookmarks",
"contacts",
"created",
"custom_field_data",
"id",
"images",
"jobs",
"journal_entries",
"last_updated",
"pk",
"subscriptions",
"tags",
]
Loading
Loading