netboxlabs-netbox-custom-objects 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netboxlabs_netbox_custom_objects-0.1.0/LICENSE.md +85 -0
- netboxlabs_netbox_custom_objects-0.1.0/PKG-INFO +61 -0
- netboxlabs_netbox_custom_objects-0.1.0/README.md +30 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/__init__.py +42 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/api/__init__.py +0 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/api/serializers.py +244 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/api/urls.py +83 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/api/views.py +49 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/choices.py +20 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/constants.py +8 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/field_types.py +796 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/fields.py +86 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/filtersets.py +21 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/forms.py +144 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/migrations/0001_initial.py +199 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/migrations/__init__.py +0 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/models.py +1056 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/navigation.py +70 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/tables.py +183 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/template_content.py +82 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/buttons/custom_objects_delete.html +4 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/netbox_custom_objects/custom_object_list.html +30 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/netbox_custom_objects/customobject.html +134 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/netbox_custom_objects/customobject_edit.html +12 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/netbox_custom_objects/customobjecttype.html +157 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/netbox_custom_objects/field_delete.html +30 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templates/netbox_custom_objects/htmx/delete_form.html +30 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templatetags/__init__.py +0 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templatetags/custom_object_buttons.py +246 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/templatetags/custom_object_utils.py +49 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/urls.py +63 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/utilities.py +85 -0
- netboxlabs_netbox_custom_objects-0.1.0/netbox_custom_objects/views.py +453 -0
- netboxlabs_netbox_custom_objects-0.1.0/netboxlabs_netbox_custom_objects.egg-info/PKG-INFO +61 -0
- netboxlabs_netbox_custom_objects-0.1.0/netboxlabs_netbox_custom_objects.egg-info/SOURCES.txt +38 -0
- netboxlabs_netbox_custom_objects-0.1.0/netboxlabs_netbox_custom_objects.egg-info/dependency_links.txt +1 -0
- netboxlabs_netbox_custom_objects-0.1.0/netboxlabs_netbox_custom_objects.egg-info/requires.txt +12 -0
- netboxlabs_netbox_custom_objects-0.1.0/netboxlabs_netbox_custom_objects.egg-info/top_level.txt +1 -0
- netboxlabs_netbox_custom_objects-0.1.0/pyproject.toml +49 -0
- netboxlabs_netbox_custom_objects-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# NetBox Limited Use License 1.0
|
|
2
|
+
|
|
3
|
+
## Acceptance
|
|
4
|
+
|
|
5
|
+
In order to get any license under these terms, you must agree to them as
|
|
6
|
+
both strict obligations and conditions to all your licenses.
|
|
7
|
+
|
|
8
|
+
## Copyright License
|
|
9
|
+
|
|
10
|
+
NetBox Labs grants you a copyright license to use and modify the software
|
|
11
|
+
only as part of a NetBox installation obtained from NetBox Labs or a NetBox
|
|
12
|
+
distributor authorized by NetBox Labs, and only for your own internal use.
|
|
13
|
+
|
|
14
|
+
For clarity, this license grants no rights to distribute the software or
|
|
15
|
+
make it available to others as part of a commercial offering.
|
|
16
|
+
|
|
17
|
+
## Patent License
|
|
18
|
+
|
|
19
|
+
NetBox Labs grants you a patent license for the software that covers patent
|
|
20
|
+
claims the licensor can license, or becomes able to license, that you would
|
|
21
|
+
infringe by using the software, as allowed in the copyright license above.
|
|
22
|
+
|
|
23
|
+
## Patent Defense
|
|
24
|
+
|
|
25
|
+
If you make any written claim that the software infringes or contributes to
|
|
26
|
+
infringement of any patent, your patent license for the software granted
|
|
27
|
+
under these terms ends immediately. If your company makes such a claim,
|
|
28
|
+
your patent license ends immediately for work on behalf of your company.
|
|
29
|
+
Competitive Restrictions
|
|
30
|
+
|
|
31
|
+
This license does not grant you the right to use the software:
|
|
32
|
+
|
|
33
|
+
- To provide a managed service or software products that includes, integrates
|
|
34
|
+
with, or extends NetBox in a way that competes with any product or service
|
|
35
|
+
of NetBox Labs.
|
|
36
|
+
|
|
37
|
+
- To assist or enable a third party in offering a service or product that
|
|
38
|
+
competes with any product or service of NetBox Labs.
|
|
39
|
+
|
|
40
|
+
## No Other Rights
|
|
41
|
+
|
|
42
|
+
These terms do not allow you to sublicense or transfer any of your licenses
|
|
43
|
+
to anyone else, or prevent NetBox Labs from granting licenses to anyone else.
|
|
44
|
+
These terms do not imply any other licenses.
|
|
45
|
+
|
|
46
|
+
## Violations
|
|
47
|
+
|
|
48
|
+
The first time you are notified in writing that you have violated any of
|
|
49
|
+
these terms, or done anything with the software not covered by your licenses,
|
|
50
|
+
your licenses can nonetheless continue if you come into full compliance with
|
|
51
|
+
these terms, and take practical steps to correct past violations, within 30
|
|
52
|
+
days of receiving notice. Otherwise, all your licenses end immediately.
|
|
53
|
+
|
|
54
|
+
## No Liability
|
|
55
|
+
|
|
56
|
+
As far as the law allows, the software comes as is, without any warranty or
|
|
57
|
+
condition, and NetBox Labs will not be liable to you for any damages arising
|
|
58
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
59
|
+
legal claim.
|
|
60
|
+
|
|
61
|
+
If this disclaimer is unenforceable under applicable law, this license is void.
|
|
62
|
+
|
|
63
|
+
## Definitions
|
|
64
|
+
|
|
65
|
+
**NetBox Labs** is NetBox Labs, Inc.
|
|
66
|
+
|
|
67
|
+
**NetBox** is the community edition of NetBox found at
|
|
68
|
+
<https://github.com/netbox-community/netbox> or any derivative thereof.
|
|
69
|
+
|
|
70
|
+
The **software** is the software NetBox Labs makes available under these terms.
|
|
71
|
+
|
|
72
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
73
|
+
|
|
74
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of
|
|
75
|
+
organization that you work for, plus all organizations that have control over,
|
|
76
|
+
are under the control of, or are under common control with that organization.
|
|
77
|
+
|
|
78
|
+
**Control** means ownership of substantially all the assets of an entity,
|
|
79
|
+
or the power to direct its management and policies by vote, contract, or
|
|
80
|
+
otherwise. Control can be direct or indirect.
|
|
81
|
+
|
|
82
|
+
**Your licenses** are all the licenses granted to you for the software under
|
|
83
|
+
these terms.
|
|
84
|
+
|
|
85
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netboxlabs-netbox-custom-objects
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A plugin to manage custom objects in NetBox
|
|
5
|
+
Author-email: NetBox Labs <support@netboxlabs.com>
|
|
6
|
+
Maintainer-email: NetBox Labs <support@netboxlabs.com>
|
|
7
|
+
License: PolyForm Shield License 1.0.0
|
|
8
|
+
Project-URL: Homepage, https://netboxlabs.com/
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE.md
|
|
20
|
+
Requires-Dist: Django
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: check-manifest; extra == "dev"
|
|
23
|
+
Requires-Dist: mkdocs; extra == "dev"
|
|
24
|
+
Requires-Dist: mkdocs-material; extra == "dev"
|
|
25
|
+
Requires-Dist: pycodestyle; extra == "dev"
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: coverage; extra == "test"
|
|
28
|
+
Requires-Dist: pytest; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# NetBox Custom Objects
|
|
33
|
+
|
|
34
|
+
This [NetBox](https://netboxlabs.com/products/netbox/) plugin introduces the ability to create new object types in NetBox so that users can add models to suit their own needs. NetBox users have been able to extend the NetBox data model for some time using both Tags & Custom Fields and Plugins. Tags and Custom Fields are easy to use, but they have limitations when used at scale, and Plugins are very powerful but require Python/Django knowledge, and ongoing maintenance. Custom Objects provides users with a no-code "sweet spot" for data model extensibility, providing a lot of the power of NetBox plugins, but with the ease of use of Tags and Custom Fields.
|
|
35
|
+
|
|
36
|
+
You can find further documentation [here](docs/index.md).
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
* NetBox v4.2 or later
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
1. Add `netboxlabs_netbox_custom_objects` to `PLUGINS` in `configuration.py`.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
PLUGINS = [
|
|
48
|
+
# ...
|
|
49
|
+
'netboxlabs_netbox_custom_objects',
|
|
50
|
+
]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
2. Run NetBox migrations:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
$ ./manage.py migrate
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Known Limitations
|
|
60
|
+
|
|
61
|
+
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.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# NetBox Custom Objects
|
|
2
|
+
|
|
3
|
+
This [NetBox](https://netboxlabs.com/products/netbox/) plugin introduces the ability to create new object types in NetBox so that users can add models to suit their own needs. NetBox users have been able to extend the NetBox data model for some time using both Tags & Custom Fields and Plugins. Tags and Custom Fields are easy to use, but they have limitations when used at scale, and Plugins are very powerful but require Python/Django knowledge, and ongoing maintenance. Custom Objects provides users with a no-code "sweet spot" for data model extensibility, providing a lot of the power of NetBox plugins, but with the ease of use of Tags and Custom Fields.
|
|
4
|
+
|
|
5
|
+
You can find further documentation [here](docs/index.md).
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
* NetBox v4.2 or later
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
1. Add `netboxlabs_netbox_custom_objects` to `PLUGINS` in `configuration.py`.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
PLUGINS = [
|
|
17
|
+
# ...
|
|
18
|
+
'netboxlabs_netbox_custom_objects',
|
|
19
|
+
]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. Run NetBox migrations:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
$ ./manage.py migrate
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Known Limitations
|
|
29
|
+
|
|
30
|
+
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.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from netbox.plugins import PluginConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Plugin Configuration
|
|
5
|
+
class CustomObjectsPluginConfig(PluginConfig):
|
|
6
|
+
name = "netbox_custom_objects"
|
|
7
|
+
verbose_name = "Custom Objects"
|
|
8
|
+
description = "A plugin to manage custom objects in NetBox"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
base_url = "custom-objects"
|
|
11
|
+
min_version = "4.2.0"
|
|
12
|
+
default_settings = {}
|
|
13
|
+
required_settings = []
|
|
14
|
+
template_extensions = "template_content.template_extensions"
|
|
15
|
+
|
|
16
|
+
# def get_model(self, model_name, require_ready=True):
|
|
17
|
+
# if require_ready:
|
|
18
|
+
# self.apps.check_models_ready()
|
|
19
|
+
# else:
|
|
20
|
+
# self.apps.check_apps_ready()
|
|
21
|
+
#
|
|
22
|
+
# if model_name.lower() in self.models:
|
|
23
|
+
# return self.models[model_name.lower()]
|
|
24
|
+
#
|
|
25
|
+
# from .models import CustomObjectType
|
|
26
|
+
# if "table" not in model_name.lower() or "model" not in model_name.lower():
|
|
27
|
+
# raise LookupError(
|
|
28
|
+
# "App '%s' doesn't have a '%s' model." % (self.label, model_name)
|
|
29
|
+
# )
|
|
30
|
+
#
|
|
31
|
+
# custom_object_type_id = int(model_name.replace("table", "").replace("model", ""))
|
|
32
|
+
#
|
|
33
|
+
# try:
|
|
34
|
+
# obj = CustomObjectType.objects.get(pk=custom_object_type_id)
|
|
35
|
+
# except CustomObjectType.DoesNotExist:
|
|
36
|
+
# raise LookupError(
|
|
37
|
+
# "App '%s' doesn't have a '%s' model." % (self.label, model_name)
|
|
38
|
+
# )
|
|
39
|
+
# return obj.get_model()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
config = CustomObjectsPluginConfig
|
|
File without changes
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from extras.choices import CustomFieldTypeChoices
|
|
3
|
+
from netbox.api.serializers import NetBoxModelSerializer
|
|
4
|
+
from rest_framework import serializers
|
|
5
|
+
from rest_framework.exceptions import ValidationError
|
|
6
|
+
from rest_framework.reverse import reverse
|
|
7
|
+
|
|
8
|
+
from netbox_custom_objects import field_types
|
|
9
|
+
from netbox_custom_objects.models import CustomObject, CustomObjectType, CustomObjectTypeField
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"CustomObjectTypeSerializer",
|
|
13
|
+
"CustomObjectSerializer",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContentTypeSerializer(NetBoxModelSerializer):
|
|
18
|
+
class Meta:
|
|
19
|
+
model = ContentType
|
|
20
|
+
fields = (
|
|
21
|
+
"id",
|
|
22
|
+
"app_label",
|
|
23
|
+
"model",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CustomObjectTypeFieldSerializer(NetBoxModelSerializer):
|
|
28
|
+
url = serializers.HyperlinkedIdentityField(
|
|
29
|
+
view_name="plugins-api:netbox_custom_objects-api:customobjecttypefield-detail"
|
|
30
|
+
)
|
|
31
|
+
app_label = serializers.CharField(required=False)
|
|
32
|
+
model = serializers.CharField(required=False)
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
model = CustomObjectTypeField
|
|
36
|
+
fields = (
|
|
37
|
+
# 'id', 'url', 'name', 'label', 'custom_object_type', 'field_type', 'content_type', 'many', 'options',
|
|
38
|
+
"id",
|
|
39
|
+
"name",
|
|
40
|
+
"label",
|
|
41
|
+
"custom_object_type",
|
|
42
|
+
"type",
|
|
43
|
+
"primary",
|
|
44
|
+
"default",
|
|
45
|
+
"choice_set",
|
|
46
|
+
"validation_regex",
|
|
47
|
+
"validation_minimum",
|
|
48
|
+
"validation_maximum",
|
|
49
|
+
"related_object_type",
|
|
50
|
+
"app_label",
|
|
51
|
+
"model",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def validate(self, attrs):
|
|
55
|
+
app_label = attrs.pop("app_label", None)
|
|
56
|
+
model = attrs.pop("model", None)
|
|
57
|
+
if attrs["type"] in [
|
|
58
|
+
CustomFieldTypeChoices.TYPE_OBJECT,
|
|
59
|
+
CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
|
60
|
+
]:
|
|
61
|
+
try:
|
|
62
|
+
attrs["related_object_type"] = ContentType.objects.get(
|
|
63
|
+
app_label=app_label, model=model
|
|
64
|
+
)
|
|
65
|
+
except ContentType.DoesNotExist:
|
|
66
|
+
raise ValidationError(
|
|
67
|
+
"Must provide valid app_label and model for object field type."
|
|
68
|
+
)
|
|
69
|
+
if attrs["type"] in [
|
|
70
|
+
CustomFieldTypeChoices.TYPE_SELECT,
|
|
71
|
+
CustomFieldTypeChoices.TYPE_MULTISELECT,
|
|
72
|
+
]:
|
|
73
|
+
if not attrs.get("choice_set", None):
|
|
74
|
+
raise ValidationError(
|
|
75
|
+
"Must provide choice_set with valid PK for select field type."
|
|
76
|
+
)
|
|
77
|
+
return super().validate(attrs)
|
|
78
|
+
|
|
79
|
+
def create(self, validated_data):
|
|
80
|
+
"""
|
|
81
|
+
Record the user who created the Custom Object as its owner.
|
|
82
|
+
"""
|
|
83
|
+
return super().create(validated_data)
|
|
84
|
+
|
|
85
|
+
def get_related_object_type(self, obj):
|
|
86
|
+
if obj.related_object_type:
|
|
87
|
+
return dict(
|
|
88
|
+
id=obj.related_object_type.id,
|
|
89
|
+
app_label=obj.related_object_type.app_label,
|
|
90
|
+
model=obj.related_object_type.model,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CustomObjectTypeSerializer(NetBoxModelSerializer):
|
|
95
|
+
url = serializers.HyperlinkedIdentityField(
|
|
96
|
+
view_name="plugins-api:netbox_custom_objects-api:customobjecttype-detail"
|
|
97
|
+
)
|
|
98
|
+
fields = CustomObjectTypeFieldSerializer(
|
|
99
|
+
nested=True,
|
|
100
|
+
read_only=True,
|
|
101
|
+
many=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
class Meta:
|
|
105
|
+
model = CustomObjectType
|
|
106
|
+
fields = [
|
|
107
|
+
"id",
|
|
108
|
+
"url",
|
|
109
|
+
"name",
|
|
110
|
+
"description",
|
|
111
|
+
"tags",
|
|
112
|
+
"created",
|
|
113
|
+
"last_updated",
|
|
114
|
+
"fields",
|
|
115
|
+
]
|
|
116
|
+
brief_fields = ("id", "url", "name", "description")
|
|
117
|
+
|
|
118
|
+
def create(self, validated_data):
|
|
119
|
+
return super().create(validated_data)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# TODO: Remove or reduce to a stub (not needed as all custom object serializers are generated via get_serializer_class)
|
|
123
|
+
class CustomObjectSerializer(NetBoxModelSerializer):
|
|
124
|
+
relation_fields = None
|
|
125
|
+
|
|
126
|
+
url = serializers.SerializerMethodField()
|
|
127
|
+
field_data = serializers.SerializerMethodField()
|
|
128
|
+
custom_object_type = CustomObjectTypeSerializer(nested=True)
|
|
129
|
+
|
|
130
|
+
class Meta:
|
|
131
|
+
model = CustomObject
|
|
132
|
+
fields = [
|
|
133
|
+
"id",
|
|
134
|
+
"url",
|
|
135
|
+
"name",
|
|
136
|
+
"display",
|
|
137
|
+
"custom_object_type",
|
|
138
|
+
"tags",
|
|
139
|
+
"created",
|
|
140
|
+
"last_updated",
|
|
141
|
+
"data",
|
|
142
|
+
"field_data",
|
|
143
|
+
]
|
|
144
|
+
brief_fields = (
|
|
145
|
+
"id",
|
|
146
|
+
"url",
|
|
147
|
+
"name",
|
|
148
|
+
"custom_object_type",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def get_display(self, obj):
|
|
152
|
+
return f"{obj.custom_object_type}: {obj.name}"
|
|
153
|
+
|
|
154
|
+
def validate(self, attrs):
|
|
155
|
+
return super().validate(attrs)
|
|
156
|
+
|
|
157
|
+
def update_relation_fields(self, instance):
|
|
158
|
+
# TODO: Implement this
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
def create(self, validated_data):
|
|
162
|
+
model = validated_data["custom_object_type"].get_model()
|
|
163
|
+
instance = model.objects.create(**validated_data)
|
|
164
|
+
|
|
165
|
+
return instance
|
|
166
|
+
|
|
167
|
+
def update(self, instance, validated_data):
|
|
168
|
+
instance = super().update(instance, validated_data)
|
|
169
|
+
# self.update_relation_fields(instance)
|
|
170
|
+
return instance
|
|
171
|
+
|
|
172
|
+
def get_url(self, obj):
|
|
173
|
+
"""
|
|
174
|
+
Given an object, return the URL that hyperlinks to the object.
|
|
175
|
+
|
|
176
|
+
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
|
|
177
|
+
attributes are not configured to correctly match the URL conf.
|
|
178
|
+
"""
|
|
179
|
+
# Unsaved objects will not yet have a valid URL.
|
|
180
|
+
if hasattr(obj, "pk") and obj.pk in (None, ""):
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
view_name = "plugins-api:netbox_custom_objects-api:customobject-detail"
|
|
184
|
+
lookup_value = getattr(obj, "pk")
|
|
185
|
+
kwargs = {
|
|
186
|
+
"pk": lookup_value,
|
|
187
|
+
"custom_object_type": obj.custom_object_type.name.lower(),
|
|
188
|
+
}
|
|
189
|
+
request = self.context["request"]
|
|
190
|
+
format = self.context.get("format")
|
|
191
|
+
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
192
|
+
|
|
193
|
+
def get_field_data(self, obj):
|
|
194
|
+
result = {}
|
|
195
|
+
return result
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def get_serializer_class(model):
|
|
199
|
+
model_fields = model.custom_object_type.fields.all()
|
|
200
|
+
meta = type(
|
|
201
|
+
"Meta",
|
|
202
|
+
(),
|
|
203
|
+
{
|
|
204
|
+
"model": model,
|
|
205
|
+
"fields": "__all__",
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def get_url(self, obj):
|
|
210
|
+
# Unsaved objects will not yet have a valid URL.
|
|
211
|
+
if hasattr(obj, "pk") and obj.pk in (None, ""):
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
view_name = "plugins-api:netbox_custom_objects-api:customobject-detail"
|
|
215
|
+
lookup_value = getattr(obj, "pk")
|
|
216
|
+
kwargs = {
|
|
217
|
+
"pk": lookup_value,
|
|
218
|
+
"custom_object_type": obj.custom_object_type.name.lower(),
|
|
219
|
+
}
|
|
220
|
+
request = self.context["request"]
|
|
221
|
+
format = self.context.get("format")
|
|
222
|
+
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
223
|
+
|
|
224
|
+
attrs = {
|
|
225
|
+
"Meta": meta,
|
|
226
|
+
"__module__": "database.serializers",
|
|
227
|
+
"url": serializers.SerializerMethodField(),
|
|
228
|
+
"get_url": get_url,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for field in model_fields:
|
|
232
|
+
field_type = field_types.FIELD_TYPE_CLASS[field.type]()
|
|
233
|
+
try:
|
|
234
|
+
attrs[field.name] = field_type.get_serializer_field(field)
|
|
235
|
+
except NotImplementedError:
|
|
236
|
+
print(f"serializer: {field.name} field is not implemented; using a default serializer field")
|
|
237
|
+
|
|
238
|
+
serializer = type(
|
|
239
|
+
f"{model._meta.object_name}Serializer",
|
|
240
|
+
(serializers.ModelSerializer,),
|
|
241
|
+
attrs,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return serializer
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from django.urls import include, path, NoReverseMatch
|
|
3
|
+
from rest_framework.response import Response
|
|
4
|
+
from rest_framework.reverse import reverse
|
|
5
|
+
from rest_framework.views import APIView
|
|
6
|
+
from netbox.api.routers import NetBoxRouter
|
|
7
|
+
from netbox_custom_objects.models import CustomObjectType
|
|
8
|
+
|
|
9
|
+
from . import views
|
|
10
|
+
|
|
11
|
+
custom_object_list = views.CustomObjectViewSet.as_view(
|
|
12
|
+
{"get": "list", "post": "create"}
|
|
13
|
+
)
|
|
14
|
+
custom_object_detail = views.CustomObjectViewSet.as_view(
|
|
15
|
+
{"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CustomObjectsAPIRootView(APIView):
|
|
20
|
+
"""
|
|
21
|
+
This is the root of the NetBox Custom Objects plugin API. Custom Object Types defined at application startup
|
|
22
|
+
are listed by lowercased name; e.g. `/api/plugins/custom-objects/cat/`.
|
|
23
|
+
"""
|
|
24
|
+
def get_view_name(self):
|
|
25
|
+
return "Custom Objects API Root"
|
|
26
|
+
|
|
27
|
+
_ignore_model_permissions = True
|
|
28
|
+
schema = None # exclude from schema
|
|
29
|
+
api_root_dict = None
|
|
30
|
+
|
|
31
|
+
# This logic is copied from stock DRF APIRootView
|
|
32
|
+
def get(self, request, *args, **kwargs):
|
|
33
|
+
# Return a plain {"name": "hyperlink"} response.
|
|
34
|
+
ret = {}
|
|
35
|
+
namespace = request.resolver_match.namespace
|
|
36
|
+
for key, url_name in self.api_root_dict.items():
|
|
37
|
+
if namespace:
|
|
38
|
+
url_name = namespace + ':' + url_name
|
|
39
|
+
try:
|
|
40
|
+
ret[key] = reverse(
|
|
41
|
+
url_name,
|
|
42
|
+
args=args,
|
|
43
|
+
kwargs=kwargs,
|
|
44
|
+
request=request,
|
|
45
|
+
format=kwargs.get('format')
|
|
46
|
+
)
|
|
47
|
+
except NoReverseMatch:
|
|
48
|
+
# Don't bail out if eg. no list routes exist, only detail routes.
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
# Extra logic to populate roots for custom object type lists
|
|
52
|
+
for custom_object_type in CustomObjectType.objects.all():
|
|
53
|
+
local_kwargs = deepcopy(kwargs)
|
|
54
|
+
cot_name = custom_object_type.name.lower()
|
|
55
|
+
url_name = 'customobject-list'
|
|
56
|
+
local_kwargs['custom_object_type'] = cot_name
|
|
57
|
+
if namespace:
|
|
58
|
+
url_name = namespace + ':' + url_name
|
|
59
|
+
ret[cot_name] = reverse(
|
|
60
|
+
url_name,
|
|
61
|
+
args=args,
|
|
62
|
+
kwargs=local_kwargs,
|
|
63
|
+
request=request,
|
|
64
|
+
format=local_kwargs.get('format')
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return Response(ret)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
router = NetBoxRouter()
|
|
71
|
+
router.APIRootView = CustomObjectsAPIRootView
|
|
72
|
+
router.register("custom-object-types", views.CustomObjectTypeViewSet)
|
|
73
|
+
router.register("custom-object-type-fields", views.CustomObjectTypeFieldViewSet)
|
|
74
|
+
|
|
75
|
+
urlpatterns = [
|
|
76
|
+
path("", include(router.urls)),
|
|
77
|
+
path("<str:custom_object_type>/", custom_object_list, name="customobject-list"),
|
|
78
|
+
path(
|
|
79
|
+
"<str:custom_object_type>/<int:pk>/",
|
|
80
|
+
custom_object_detail,
|
|
81
|
+
name="customobject-detail",
|
|
82
|
+
),
|
|
83
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from django.http import Http404
|
|
2
|
+
from rest_framework.routers import APIRootView
|
|
3
|
+
from rest_framework.viewsets import ModelViewSet
|
|
4
|
+
|
|
5
|
+
from netbox_custom_objects.models import CustomObject, CustomObjectType, CustomObjectTypeField
|
|
6
|
+
|
|
7
|
+
from . import serializers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RootView(APIRootView):
|
|
11
|
+
def get_view_name(self):
|
|
12
|
+
return "CustomObjects"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CustomObjectTypeViewSet(ModelViewSet):
|
|
16
|
+
queryset = CustomObjectType.objects.all()
|
|
17
|
+
serializer_class = serializers.CustomObjectTypeSerializer
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CustomObjectViewSet(ModelViewSet):
|
|
21
|
+
queryset = CustomObject.objects.all()
|
|
22
|
+
serializer_class = serializers.CustomObjectSerializer
|
|
23
|
+
model = None
|
|
24
|
+
|
|
25
|
+
def get_view_name(self):
|
|
26
|
+
if self.model:
|
|
27
|
+
return self.model.custom_object_type.name
|
|
28
|
+
return super().get_view_name()
|
|
29
|
+
|
|
30
|
+
def get_serializer_class(self):
|
|
31
|
+
return serializers.get_serializer_class(self.model)
|
|
32
|
+
|
|
33
|
+
def get_queryset(self):
|
|
34
|
+
try:
|
|
35
|
+
custom_object_type = CustomObjectType.objects.get(
|
|
36
|
+
name__iexact=self.kwargs["custom_object_type"]
|
|
37
|
+
)
|
|
38
|
+
except CustomObjectType.DoesNotExist:
|
|
39
|
+
raise Http404
|
|
40
|
+
self.model = custom_object_type.get_model()
|
|
41
|
+
return self.model.objects.all()
|
|
42
|
+
|
|
43
|
+
def list(self, request, *args, **kwargs):
|
|
44
|
+
return super().list(request, *args, **kwargs)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CustomObjectTypeFieldViewSet(ModelViewSet):
|
|
48
|
+
queryset = CustomObjectTypeField.objects.all()
|
|
49
|
+
serializer_class = serializers.CustomObjectTypeFieldSerializer
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from utilities.choices import ChoiceSet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MappingFieldTypeChoices(ChoiceSet):
|
|
6
|
+
CHAR = "char"
|
|
7
|
+
INTEGER = "integer"
|
|
8
|
+
BOOLEAN = "boolean"
|
|
9
|
+
DATE = "date"
|
|
10
|
+
DATETIME = "datetime"
|
|
11
|
+
OBJECT = "object"
|
|
12
|
+
|
|
13
|
+
CHOICES = (
|
|
14
|
+
(CHAR, _("String"), "cyan"),
|
|
15
|
+
(INTEGER, _("Integer"), "orange"),
|
|
16
|
+
(BOOLEAN, _("Boolean"), "green"),
|
|
17
|
+
(DATE, _("Date"), "red"),
|
|
18
|
+
(DATETIME, _("DateTime"), "blue"),
|
|
19
|
+
(OBJECT, _("Object"), "orange"),
|
|
20
|
+
)
|