diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 5b4e4e5a4..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [jeremystretch] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index a83e9b34e..07502c78a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,25 +1,29 @@ --- name: 🐛 Bug Report -about: Report a reproducible bug in the current release of NetBox +description: Report a reproducible bug in the current release of NetBox labels: ["type: bug"] body: - type: markdown attributes: - value: "**NOTE:** This form is only for reporting _reproducible bugs_ in a - current NetBox installation. If you're having trouble with installation or just - looking for assistance with using NetBox, please visit our - [discussion forum](https://github.com/netbox-community/netbox/discussions) instead." + value: > + **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox + installation. If you're having trouble with installation or just looking for + assistance with using NetBox, please visit our + [discussion forum](https://github.com/netbox-community/netbox/discussions) instead. - type: input attributes: label: NetBox version - description: "What version of NetBox are you currently running?" - placeholder: v2.10.4 + description: > + What version of NetBox are you currently running? (If you don't have access to the most + recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/) + before opening a bug report to see if your issue has already been addressed.) + placeholder: v2.11.12 validations: required: true - type: dropdown attributes: label: Python version - description: "What version of Python are you currently running?" + description: What version of Python are you currently running? options: - 3.6 - 3.7 @@ -30,12 +34,14 @@ body: - type: textarea attributes: label: Steps to Reproduce - description: "Describe in detail the exact steps that someone else can take to - reproduce this bug using the current stable release of NetBox. Begin with the - creation of any necessary database objects and call out every operation being - performed explicitly. If reporting a bug in the REST API, be sure to reconstruct - the raw HTTP request(s) being made: Don't rely on a client library such as - pynetbox." + description: > + Describe in detail the exact steps that someone else can take to + reproduce this bug using the current stable release of NetBox. Begin with the + creation of any necessary database objects and call out every operation being + performed explicitly. If reporting a bug in the REST API, be sure to reconstruct + the raw HTTP request(s) being made: Don't rely on a client library such as + pynetbox. Additionally, **do not rely on the demo instance** for reproducing + suspected bugs, as its data is prone to modification or deletion at any time. placeholder: | 1. Click on "create widget" 2. Set foo to 12 and bar to G @@ -45,19 +51,14 @@ body: - type: textarea attributes: label: Expected Behavior - description: "What did you expect to happen?" - placeholder: "A new widget should have been created with the specified attributes" + description: What did you expect to happen? + placeholder: A new widget should have been created with the specified attributes validations: required: true - type: textarea attributes: label: Observed Behavior - description: "What happened instead?" - placeholder: "A TypeError exception was raised" + description: What happened instead? + placeholder: A TypeError exception was raised validations: required: true - - type: markdown - attributes: - value: | - ### Additional information - You can use the space below to provide any additional information or to attach files. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3e1d0167d..1f8fdebd4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,7 +3,10 @@ blank_issues_enabled: false contact_links: - name: 📖 Contributing Policy url: https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md - about: Please read through our contributing policy before opening an issue or pull request - - name: 💬 Discussion Group - url: https://groups.google.com/g/netbox-discuss - about: Join our discussion group for assistance with installation issues and other problems + about: "Please read through our contributing policy before opening an issue or pull request" + - name: ❓ Discussion + url: https://github.com/netbox-community/netbox/discussions + about: "If you're just looking for help, try starting a discussion instead" + - name: 💬 Community Slack + url: https://netdev.chat/ + about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems" diff --git a/.github/ISSUE_TEMPLATE/documentation_change.yaml b/.github/ISSUE_TEMPLATE/documentation_change.yaml index 3b2026b34..0f87115fc 100644 --- a/.github/ISSUE_TEMPLATE/documentation_change.yaml +++ b/.github/ISSUE_TEMPLATE/documentation_change.yaml @@ -1,6 +1,6 @@ --- name: 📖 Documentation Change -about: Suggest an addition or modification to the NetBox documentation +description: Suggest an addition or modification to the NetBox documentation labels: ["type: documentation"] body: - type: dropdown @@ -14,25 +14,22 @@ body: - Cleanup (formatting, typos, etc.) validations: required: true - - type: checkboxes + - type: dropdown attributes: label: Area - description: To what section(s) of the documentation does this change pertain? + description: To what section of the documentation does this change primarily pertain? options: - - label: Installation instructions - - label: Configuration parameters - - label: Functionality/features - - label: REST API - - label: Administration/development - - label: Other + - Installation instructions + - Configuration parameters + - Functionality/features + - REST API + - Administration/development + - Other + validations: + required: true - type: textarea attributes: label: Proposed Changes - description: "Describe the proposed changes and why they are necessary" + description: Describe the proposed changes and why they are necessary. validations: required: true - - type: markdown - attributes: - value: | - ### Additional information - You can use the space below to provide any additional information or to attach files. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index efa83b376..f7b616907 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,19 +1,20 @@ --- name: ✨ Feature Request -about: Propose a new NetBox feature or enhancement +description: Propose a new NetBox feature or enhancement labels: ["type: feature"] body: - type: markdown attributes: - value: "**NOTE:** This form is only for submitting well-formed proposals to extend or - modify NetBox in some way. If you're trying to solve a problem but can't figure out how, - or if you still need time to work on the details of a proposed new feature, please start - a [discussion](https://github.com/netbox-community/netbox/discussions) instead." + value: > + **NOTE:** This form is only for submitting well-formed proposals to extend or modify + NetBox in some way. If you're trying to solve a problem but can't figure out how, or if + you still need time to work on the details of a proposed new feature, please start a + [discussion](https://github.com/netbox-community/netbox/discussions) instead. - type: input attributes: label: NetBox version - description: "What version of NetBox are you currently running?" - placeholder: v2.10.4 + description: What version of NetBox are you currently running? + placeholder: v2.11.12 validations: required: true - type: dropdown @@ -28,31 +29,29 @@ body: - type: textarea attributes: label: Proposed functionality - description: "Describe in detail the new feature or behavior you'd like to propose. - Include any specific changes to work flows, data models, or the user interface." + description: > + Describe in detail the new feature or behavior you'd like to propose. Include any specific + changes to work flows, data models, or the user interface. validations: required: true - type: textarea attributes: label: Use case - description: "Explain how adding this functionality would benefit NetBox users. What - need does it address?" + description: > + Explain how adding this functionality would benefit NetBox users. What need does it address? validations: required: true - type: textarea attributes: label: Database changes - description: "Note any changes to the database schema necessary to support the new - feature. For example, does the proposal require adding a new model or field? (Not - all new features require database changes.)" + description: > + Note any changes to the database schema necessary to support the new feature. For example, + does the proposal require adding a new model or field? (Not all new features require database + changes.) - type: textarea attributes: label: External dependencies - description: "List any new dependencies on external libraries or services that this - new feature would introduce. For example, does the proposal require the installation - of a new Python package? (Not all new features introduce new dependencies.)" - - type: markdown - attributes: - value: | - ### Additional information - You can use the space below to provide any additional information or to attach files. + description: > + List any new dependencies on external libraries or services that this new feature would + introduce. For example, does the proposal require the installation of a new Python package? + (Not all new features introduce new dependencies.) diff --git a/.github/ISSUE_TEMPLATE/housekeeping.yaml b/.github/ISSUE_TEMPLATE/housekeeping.yaml index 0f466aa24..777871395 100644 --- a/.github/ISSUE_TEMPLATE/housekeeping.yaml +++ b/.github/ISSUE_TEMPLATE/housekeeping.yaml @@ -1,27 +1,24 @@ --- name: 🏡 Housekeeping -about: A change pertaining to the codebase itself (developers only) +description: A change pertaining to the codebase itself (developers only) labels: ["type: housekeeping"] body: - type: markdown attributes: - value: "**NOTE:** This template is for use by maintainers only. Please do not submit - an issue using this template unless you have been specifically asked to do so." + value: > + **NOTE:** This template is for use by maintainers only. Please do not submit + an issue using this template unless you have been specifically asked to do so. - type: textarea attributes: label: Proposed Changes - description: "Describe in detail the new feature or behavior you'd like to propose. - Include any specific changes to work flows, data models, or the user interface." + description: > + Describe in detail the new feature or behavior you'd like to propose. + Include any specific changes to work flows, data models, or the user interface. validations: required: true - type: textarea attributes: label: Justification - description: "Please provide justification for the proposed change(s)." + description: Please provide justification for the proposed change(s). validations: required: true - - type: markdown - attributes: - value: | - ### Additional information - You can use the space below to provide any additional information or to attach files. diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 92da07e6a..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Configuration for Stale (https://github.com/apps/stale) - -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 45 - -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 15 - -# Issues with these labels will never be considered stale -exemptLabels: - - "status: accepted" - - "status: blocked" - - "status: needs milestone" - -# Label to use when marking an issue as stale -staleLabel: "pending closure" - -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. NetBox - is governed by a small group of core maintainers which means not all opened - issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md). - -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - This issue has been automatically closed due to lack of activity. In an - effort to reduce noise, please do not comment any further. Note that the - core maintainers may elect to reopen this issue at a later date if deemed - necessary. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..d8099923f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,35 @@ +# close-stale-issues (https://github.com/marketplace/actions/close-stale-issues) +name: 'Close stale issues/PRs' +on: + schedule: + - cron: '0 4 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + close-issue-message: > + This issue has been automatically closed due to lack of activity. In an + effort to reduce noise, please do not comment any further. Note that the + core maintainers may elect to reopen this issue at a later date if deemed + necessary. + close-pr-message: > + This PR has been automatically closed due to lack of activity. + days-before-stale: 60 + days-before-close: 30 + exempt-issue-labels: 'status: accepted,status: blocked,status: needs milestone' + operations-per-run: 100 + remove-stale-when-updated: false + stale-issue-label: 'pending closure' + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. NetBox + is governed by a small group of core maintainers which means not all opened + issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md). + stale-pr-label: 'pending closure' + stale-pr-message: > + This PR has been automatically marked as stale because it has not had + recent activity. It will be closed automatically if no further action is + taken. diff --git a/.gitignore b/.gitignore index 3ec36e925..56a6ac06a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.swp /netbox/netbox/configuration.py /netbox/netbox/ldap_config.py +/netbox/project-static/.cache +/netbox/project-static/node_modules /netbox/reports/* !/netbox/reports/__init__.py /netbox/scripts/* diff --git a/.jenkins b/.jenkins index 3b12d0aa7..4441b0103 100644 --- a/.jenkins +++ b/.jenkins @@ -13,6 +13,7 @@ pythonPipeline([ 'pythonVersion': '3.7', 'skipDocs': true, 'skipLint': true, + 'skipUnitTest': true, 'skipIntegrationTest': true, 'skipPrivateRepo': true, 'podTemplate': """ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd62c5ab7..7a3b1f002 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ discussions. ### Slack -For real-time chat, you can join the **#netbox** Slack channel on [NetDev Community](https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ). +For real-time chat, you can join the **#netbox** Slack channel on [NetDev Community](https://netdev.chat/). Unfortunately, the Slack channel does not provide long-term retention of chat history, so try to avoid it for any discussions would benefit from being preserved for future reference. @@ -160,17 +160,20 @@ accumulating a large backlog of work. The core maintainers group has chosen to make use of GitHub's [Stale bot](https://github.com/apps/stale) to aid in issue management. -* Issues will be marked as stale after 45 days of no activity. -* Then after 15 more days of inactivity, the issue will be closed. +* Issues will be marked as stale after 60 days of no activity. +* If the stable label is not removed in the following 30 days, the issue will + be closed automatically. * Any issue bearing one of the following labels will be exempt from all Stale bot actions: * `status: accepted` * `status: blocked` * `status: needs milestone` -It is natural that some new issues get more attention than others. Stale bot -helps bring renewed attention to potentially valuable issues that may have been -overlooked. +It is natural that some new issues get more attention than others. The stale +bot helps bring renewed attention to potentially valuable issues that may have +been overlooked. **Do not** comment on an issue that has been marked stale in +an effort to circumvent the bot: Doing so will not remove the stale label. +(Stale labels can be removed only by maintainers.) ## Maintainer Guidance diff --git a/README.md b/README.md index 880fa8c08..cb1991447 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ - +
{{ obj }}
. Links '
- 'which render as empty text will not be displayed.',
- 'url': 'Jinja2 template code for the link URL. Reference the object as {{ obj }}
.',
+ 'link_text': 'Jinja2 template code for the link text. Reference the object as {{ obj }}
. '
+ 'Links which render as empty text will not be displayed.',
+ 'link_url': 'Jinja2 template code for the link URL. Reference the object as {{ obj }}
.',
}
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # Format ContentType choices
- order_content_types(self.fields['content_type'])
- self.fields['content_type'].choices.insert(0, ('', '---------'))
-
@admin.register(CustomLink)
class CustomLinkAdmin(admin.ModelAdmin):
@@ -158,7 +149,7 @@ class CustomLinkAdmin(admin.ModelAdmin):
'fields': ('content_type', 'name', 'group_name', 'weight', 'button_class', 'new_window')
}),
('Templates', {
- 'fields': ('text', 'url'),
+ 'fields': ('link_text', 'link_url'),
'classes': ('monospace',)
})
)
@@ -176,24 +167,21 @@ class CustomLinkAdmin(admin.ModelAdmin):
#
class ExportTemplateForm(forms.ModelForm):
+ content_type = ContentTypeChoiceField(
+ queryset=ContentType.objects.all(),
+ limit_choices_to=FeatureQuery('custom_links')
+ )
class Meta:
model = ExportTemplate
exclude = []
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # Format ContentType choices
- order_content_types(self.fields['content_type'])
- self.fields['content_type'].choices.insert(0, ('', '---------'))
-
@admin.register(ExportTemplate)
class ExportTemplateAdmin(admin.ModelAdmin):
fieldsets = (
('Export Template', {
- 'fields': ('content_type', 'name', 'description', 'mime_type', 'file_extension')
+ 'fields': ('content_type', 'name', 'description', 'mime_type', 'file_extension', 'as_attachment')
}),
('Content', {
'fields': ('template_code',),
@@ -201,7 +189,7 @@ class ExportTemplateAdmin(admin.ModelAdmin):
})
)
list_display = [
- 'name', 'content_type', 'description', 'mime_type', 'file_extension',
+ 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
]
list_filter = [
'content_type',
diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py
index c8c4ba89e..5cb1fc276 100644
--- a/netbox/extras/api/customfields.py
+++ b/netbox/extras/api/customfields.py
@@ -1,9 +1,7 @@
from django.contrib.contenttypes.models import ContentType
-from rest_framework.fields import CreateOnlyDefault, Field
+from rest_framework.fields import Field
-from extras.choices import *
from extras.models import CustomField
-from netbox.api import ValidatedModelSerializer
#
@@ -56,34 +54,3 @@ class CustomFieldsDataField(Field):
data = {**self.parent.instance.custom_field_data, **data}
return data
-
-
-class CustomFieldModelSerializer(ValidatedModelSerializer):
- """
- Extends ModelSerializer to render any CustomFields and their values associated with an object.
- """
- custom_fields = CustomFieldsDataField(
- source='custom_field_data',
- default=CreateOnlyDefault(CustomFieldDefaultValues())
- )
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- if self.instance is not None:
-
- # Retrieve the set of CustomFields which apply to this type of object
- content_type = ContentType.objects.get_for_model(self.Meta.model)
- fields = CustomField.objects.filter(content_types=content_type)
-
- # Populate CustomFieldValues for each instance from database
- if type(self.instance) in (list, tuple):
- for obj in self.instance:
- self._populate_custom_fields(obj, fields)
- else:
- self._populate_custom_fields(self.instance, fields)
-
- def _populate_custom_fields(self, instance, custom_fields):
- instance.custom_fields = {}
- for field in custom_fields:
- instance.custom_fields[field.name] = instance.cf.get(field.name)
diff --git a/netbox/extras/api/nested_serializers.py b/netbox/extras/api/nested_serializers.py
index 5635f401b..4acde31ab 100644
--- a/netbox/extras/api/nested_serializers.py
+++ b/netbox/extras/api/nested_serializers.py
@@ -2,24 +2,44 @@ from rest_framework import serializers
from extras import choices, models
from netbox.api import ChoiceField, WritableNestedSerializer
+from netbox.api.serializers import NestedTagSerializer
from users.api.nested_serializers import NestedUserSerializer
__all__ = [
'NestedConfigContextSerializer',
'NestedCustomFieldSerializer',
+ 'NestedCustomLinkSerializer',
'NestedExportTemplateSerializer',
'NestedImageAttachmentSerializer',
'NestedJobResultSerializer',
- 'NestedTagSerializer',
+ 'NestedJournalEntrySerializer',
+ 'NestedTagSerializer', # Defined in netbox.api.serializers
+ 'NestedWebhookSerializer',
]
+class NestedWebhookSerializer(WritableNestedSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='extras-api:webhook-detail')
+
+ class Meta:
+ model = models.Webhook
+ fields = ['id', 'url', 'display', 'name']
+
+
class NestedCustomFieldSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
class Meta:
model = models.CustomField
- fields = ['id', 'url', 'name']
+ fields = ['id', 'url', 'display', 'name']
+
+
+class NestedCustomLinkSerializer(WritableNestedSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='extras-api:customlink-detail')
+
+ class Meta:
+ model = models.CustomLink
+ fields = ['id', 'url', 'display', 'name']
class NestedConfigContextSerializer(WritableNestedSerializer):
@@ -27,7 +47,7 @@ class NestedConfigContextSerializer(WritableNestedSerializer):
class Meta:
model = models.ConfigContext
- fields = ['id', 'url', 'name']
+ fields = ['id', 'url', 'display', 'name']
class NestedExportTemplateSerializer(WritableNestedSerializer):
@@ -35,7 +55,7 @@ class NestedExportTemplateSerializer(WritableNestedSerializer):
class Meta:
model = models.ExportTemplate
- fields = ['id', 'url', 'name']
+ fields = ['id', 'url', 'display', 'name']
class NestedImageAttachmentSerializer(WritableNestedSerializer):
@@ -43,15 +63,15 @@ class NestedImageAttachmentSerializer(WritableNestedSerializer):
class Meta:
model = models.ImageAttachment
- fields = ['id', 'url', 'name', 'image']
+ fields = ['id', 'url', 'display', 'name', 'image']
-class NestedTagSerializer(WritableNestedSerializer):
- url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail')
+class NestedJournalEntrySerializer(WritableNestedSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
class Meta:
- model = models.Tag
- fields = ['id', 'url', 'name', 'slug', 'color']
+ model = models.JournalEntry
+ fields = ['id', 'url', 'display', 'created']
class NestedJobResultSerializer(serializers.ModelSerializer):
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index a85ca05b7..66627bfbc 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -4,17 +4,16 @@ from drf_yasg.utils import swagger_serializer_method
from rest_framework import serializers
from dcim.api.nested_serializers import (
- NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedRackSerializer,
- NestedRegionSerializer, NestedSiteSerializer,
+ NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer,
+ NestedRackSerializer, NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
)
-from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
+from dcim.models import Device, DeviceRole, DeviceType, Platform, Rack, Region, Site, SiteGroup
from extras.choices import *
-from extras.models import (
- ConfigContext, CustomField, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
-)
+from extras.models import *
from extras.utils import FeatureQuery
-from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
+from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.exceptions import SerializerNotFound
+from netbox.api.serializers import BaseModelSerializer, ValidatedModelSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
from tenancy.models import Tenant, TenantGroup
from users.api.nested_serializers import NestedUserSerializer
@@ -23,6 +22,46 @@ from virtualization.api.nested_serializers import NestedClusterGroupSerializer,
from virtualization.models import Cluster, ClusterGroup
from .nested_serializers import *
+__all__ = (
+ 'ConfigContextSerializer',
+ 'ContentTypeSerializer',
+ 'CustomFieldSerializer',
+ 'CustomLinkSerializer',
+ 'ExportTemplateSerializer',
+ 'ImageAttachmentSerializer',
+ 'JobResultSerializer',
+ 'ObjectChangeSerializer',
+ 'ReportDetailSerializer',
+ 'ReportSerializer',
+ 'ScriptDetailSerializer',
+ 'ScriptInputSerializer',
+ 'ScriptLogMessageSerializer',
+ 'ScriptOutputSerializer',
+ 'ScriptSerializer',
+ 'TagSerializer',
+ 'WebhookSerializer',
+)
+
+
+#
+# Webhooks
+#
+
+class WebhookSerializer(ValidatedModelSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='extras-api:webhook-detail')
+ content_types = ContentTypeField(
+ queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()),
+ many=True
+ )
+
+ class Meta:
+ model = Webhook
+ fields = [
+ 'id', 'url', 'display', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url',
+ 'enabled', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
+ 'ssl_verification', 'ca_file_path',
+ ]
+
#
# Custom fields
@@ -40,11 +79,29 @@ class CustomFieldSerializer(ValidatedModelSerializer):
class Meta:
model = CustomField
fields = [
- 'id', 'url', 'content_types', 'type', 'name', 'label', 'description', 'required', 'filter_logic',
+ 'id', 'url', 'display', 'content_types', 'type', 'name', 'label', 'description', 'required', 'filter_logic',
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices',
]
+#
+# Custom links
+#
+
+class CustomLinkSerializer(ValidatedModelSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='extras-api:customlink-detail')
+ content_type = ContentTypeField(
+ queryset=ContentType.objects.filter(FeatureQuery('custom_links').get_query())
+ )
+
+ class Meta:
+ model = CustomLink
+ fields = [
+ 'id', 'url', 'display', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name',
+ 'button_class', 'new_window',
+ ]
+
+
#
# Export templates
#
@@ -57,7 +114,10 @@ class ExportTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = ExportTemplate
- fields = ['id', 'url', 'content_type', 'name', 'description', 'template_code', 'mime_type', 'file_extension']
+ fields = [
+ 'id', 'url', 'display', 'content_type', 'name', 'description', 'template_code', 'mime_type',
+ 'file_extension', 'as_attachment',
+ ]
#
@@ -70,39 +130,7 @@ class TagSerializer(ValidatedModelSerializer):
class Meta:
model = Tag
- fields = ['id', 'url', 'name', 'slug', 'color', 'description', 'tagged_items']
-
-
-class TaggedObjectSerializer(serializers.Serializer):
- tags = NestedTagSerializer(many=True, required=False)
-
- def create(self, validated_data):
- tags = validated_data.pop('tags', None)
- instance = super().create(validated_data)
-
- if tags is not None:
- return self._save_tags(instance, tags)
- return instance
-
- def update(self, instance, validated_data):
- tags = validated_data.pop('tags', None)
-
- # Cache tags on instance for change logging
- instance._tags = tags or []
-
- instance = super().update(instance, validated_data)
-
- if tags is not None:
- return self._save_tags(instance, tags)
- return instance
-
- def _save_tags(self, instance, tags):
- if tags:
- instance.tags.set(*[t.name for t in tags])
- else:
- instance.tags.clear()
-
- return instance
+ fields = ['id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tagged_items']
#
@@ -119,8 +147,8 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
class Meta:
model = ImageAttachment
fields = [
- 'id', 'url', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height', 'image_width',
- 'created',
+ 'id', 'url', 'display', 'content_type', 'object_id', 'parent', 'name', 'image', 'image_height',
+ 'image_width', 'created',
]
def validate(self, data):
@@ -154,6 +182,51 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
return serializer(obj.parent, context={'request': self.context['request']}).data
+#
+# Journal entries
+#
+
+class JournalEntrySerializer(ValidatedModelSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
+ assigned_object_type = ContentTypeField(
+ queryset=ContentType.objects.all()
+ )
+ assigned_object = serializers.SerializerMethodField(read_only=True)
+ kind = ChoiceField(
+ choices=JournalEntryKindChoices,
+ required=False
+ )
+
+ class Meta:
+ model = JournalEntry
+ fields = [
+ 'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created',
+ 'created_by', 'kind', 'comments',
+ ]
+
+ def validate(self, data):
+
+ # Validate that the parent object exists
+ if 'assigned_object_type' in data and 'assigned_object_id' in data:
+ try:
+ data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
+ except ObjectDoesNotExist:
+ raise serializers.ValidationError(
+ f"Invalid assigned_object: {data['assigned_object_type']} ID {data['assigned_object_id']}"
+ )
+
+ # Enforce model validation
+ super().validate(data)
+
+ return data
+
+ @swagger_serializer_method(serializer_or_field=serializers.DictField)
+ def get_assigned_object(self, instance):
+ serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), prefix='Nested')
+ context = {'request': self.context['request']}
+ return serializer(instance.assigned_object, context=context).data
+
+
#
# Config contexts
#
@@ -166,12 +239,24 @@ class ConfigContextSerializer(ValidatedModelSerializer):
required=False,
many=True
)
+ site_groups = SerializedPKRelatedField(
+ queryset=SiteGroup.objects.all(),
+ serializer=NestedSiteGroupSerializer,
+ required=False,
+ many=True
+ )
sites = SerializedPKRelatedField(
queryset=Site.objects.all(),
serializer=NestedSiteSerializer,
required=False,
many=True
)
+ device_types = SerializedPKRelatedField(
+ queryset=DeviceType.objects.all(),
+ serializer=NestedDeviceTypeSerializer,
+ required=False,
+ many=True
+ )
roles = SerializedPKRelatedField(
queryset=DeviceRole.objects.all(),
serializer=NestedDeviceRoleSerializer,
@@ -218,8 +303,9 @@ class ConfigContextSerializer(ValidatedModelSerializer):
class Meta:
model = ConfigContext
fields = [
- 'id', 'url', 'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms',
- 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data', 'created', 'last_updated',
+ 'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
+ 'device_types', 'roles', 'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
+ 'data', 'created', 'last_updated',
]
@@ -227,7 +313,7 @@ class ConfigContextSerializer(ValidatedModelSerializer):
# Job Results
#
-class JobResultSerializer(serializers.ModelSerializer):
+class JobResultSerializer(BaseModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:jobresult-detail')
user = NestedUserSerializer(
read_only=True
@@ -240,7 +326,7 @@ class JobResultSerializer(serializers.ModelSerializer):
class Meta:
model = JobResult
fields = [
- 'id', 'url', 'created', 'completed', 'name', 'obj_type', 'status', 'user', 'data', 'job_id',
+ 'id', 'url', 'display', 'created', 'completed', 'name', 'obj_type', 'status', 'user', 'data', 'job_id',
]
@@ -318,7 +404,7 @@ class ScriptOutputSerializer(serializers.Serializer):
# Change logging
#
-class ObjectChangeSerializer(serializers.ModelSerializer):
+class ObjectChangeSerializer(BaseModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:objectchange-detail')
user = NestedUserSerializer(
read_only=True
@@ -337,8 +423,8 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
class Meta:
model = ObjectChange
fields = [
- 'id', 'url', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type',
- 'changed_object_id', 'changed_object', 'object_data',
+ 'id', 'url', 'display', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type',
+ 'changed_object_id', 'changed_object', 'prechange_data', 'postchange_data',
]
@swagger_serializer_method(serializer_or_field=serializers.DictField)
@@ -365,13 +451,13 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
# ContentTypes
#
-class ContentTypeSerializer(serializers.ModelSerializer):
+class ContentTypeSerializer(BaseModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
display_name = serializers.SerializerMethodField()
class Meta:
model = ContentType
- fields = ['id', 'url', 'app_label', 'model', 'display_name']
+ fields = ['id', 'url', 'display', 'app_label', 'model', 'display_name']
@swagger_serializer_method(serializer_or_field=serializers.CharField)
def get_display_name(self, obj):
diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py
index da62b3d72..565f2cdc7 100644
--- a/netbox/extras/api/urls.py
+++ b/netbox/extras/api/urls.py
@@ -5,9 +5,15 @@ from . import views
router = OrderedDefaultRouter()
router.APIRootView = views.ExtrasRootView
+# Webhooks
+router.register('webhooks', views.WebhookViewSet)
+
# Custom fields
router.register('custom-fields', views.CustomFieldViewSet)
+# Custom links
+router.register('custom-links', views.CustomLinkViewSet)
+
# Export templates
router.register('export-templates', views.ExportTemplateViewSet)
@@ -17,6 +23,9 @@ router.register('tags', views.TagViewSet)
# Image attachments
router.register('image-attachments', views.ImageAttachmentViewSet)
+# Journal entries
+router.register('journal-entries', views.JournalEntryViewSet)
+
# Config contexts
router.register('config-contexts', views.ConfigContextViewSet)
diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py
index 1067ac0d3..fbeba8328 100644
--- a/netbox/extras/api/views.py
+++ b/netbox/extras/api/views.py
@@ -9,11 +9,9 @@ from rest_framework.routers import APIRootView
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
from rq import Worker
-from extras import filters
+from extras import filtersets
from extras.choices import JobResultStatusChoices
-from extras.models import (
- ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem,
-)
+from extras.models import *
from extras.models import CustomField
from extras.reports import get_report, get_reports, run_report
from extras.scripts import get_script, get_scripts, run_script
@@ -55,6 +53,17 @@ class ConfigContextQuerySetMixin:
return queryset.annotate_config_context_data()
+#
+# Webhooks
+#
+
+class WebhookViewSet(ModelViewSet):
+ metadata_class = ContentTypeMetadata
+ queryset = Webhook.objects.all()
+ serializer_class = serializers.WebhookSerializer
+ filterset_class = filtersets.WebhookFilterSet
+
+
#
# Custom fields
#
@@ -63,7 +72,7 @@ class CustomFieldViewSet(ModelViewSet):
metadata_class = ContentTypeMetadata
queryset = CustomField.objects.all()
serializer_class = serializers.CustomFieldSerializer
- filterset_class = filters.CustomFieldFilterSet
+ filterset_class = filtersets.CustomFieldFilterSet
class CustomFieldModelViewSet(ModelViewSet):
@@ -84,6 +93,17 @@ class CustomFieldModelViewSet(ModelViewSet):
return context
+#
+# Custom links
+#
+
+class CustomLinkViewSet(ModelViewSet):
+ metadata_class = ContentTypeMetadata
+ queryset = CustomLink.objects.all()
+ serializer_class = serializers.CustomLinkSerializer
+ filterset_class = filtersets.CustomLinkFilterSet
+
+
#
# Export templates
#
@@ -92,7 +112,7 @@ class ExportTemplateViewSet(ModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ExportTemplate.objects.all()
serializer_class = serializers.ExportTemplateSerializer
- filterset_class = filters.ExportTemplateFilterSet
+ filterset_class = filtersets.ExportTemplateFilterSet
#
@@ -104,7 +124,7 @@ class TagViewSet(ModelViewSet):
tagged_items=count_related(TaggedItem, 'tag')
)
serializer_class = serializers.TagSerializer
- filterset_class = filters.TagFilterSet
+ filterset_class = filtersets.TagFilterSet
#
@@ -115,7 +135,18 @@ class ImageAttachmentViewSet(ModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ImageAttachment.objects.all()
serializer_class = serializers.ImageAttachmentSerializer
- filterset_class = filters.ImageAttachmentFilterSet
+ filterset_class = filtersets.ImageAttachmentFilterSet
+
+
+#
+# Journal entries
+#
+
+class JournalEntryViewSet(ModelViewSet):
+ metadata_class = ContentTypeMetadata
+ queryset = JournalEntry.objects.all()
+ serializer_class = serializers.JournalEntrySerializer
+ filterset_class = filtersets.JournalEntryFilterSet
#
@@ -124,10 +155,10 @@ class ImageAttachmentViewSet(ModelViewSet):
class ConfigContextViewSet(ModelViewSet):
queryset = ConfigContext.objects.prefetch_related(
- 'regions', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
+ 'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
)
serializer_class = serializers.ConfigContextSerializer
- filterset_class = filters.ConfigContextFilterSet
+ filterset_class = filtersets.ConfigContextFilterSet
#
@@ -208,7 +239,7 @@ class ReportViewSet(ViewSet):
Run a Report identified as "{{ obj.circuit.provider }}
+{{ obj.circuit.cid }}
+{{ form.term_side.value }}
+Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Circuits | ++ {{ circuits_table.rows|length }} + | +
Site | -- {% if termination.site.region %} - {{ termination.site.region }} / - {% endif %} - {{ termination.site }} - | -||
Termination | -
- {% if termination.cable %}
- {% if perms.dcim.delete_cable %}
-
-
- Disconnect
-
-
+ {% if termination.site %}
+ | ||
Site | ++ {% if termination.site.region %} + {{ termination.site.region }} / {% endif %} - {{ termination.cable }} - - - - {% with peer=termination.get_cable_peer %} - to - {% if peer.device %} - {{ peer.device }} - {% elif peer.circuit %} - {{ peer.circuit }} + {{ termination.site }} + | +||
Termination | +
+ {% if termination.mark_connected %}
+
+ Marked as connected
+ {% elif termination.cable %}
+ {% if perms.dcim.delete_cable %}
+
+
+ Disconnect
+
+
{% endif %}
- ({{ peer }})
- {% endwith %}
- {% else %}
- {% if perms.dcim.add_cable %}
-
-
-
-
-
-
+ {{ termination.cable }}
+
+
+
+ {% with peer=termination.get_cable_peer %}
+ to {{ peer.parent_object }}
+ / {% if peer.get_absolute_url %}{{ peer }}{% else %}{{ peer }}{% endif %}
+ {% endwith %}
+ {% else %}
+ {% if perms.dcim.add_cable %}
+
+
+
+
+
+
+ {% endif %}
+ Not defined
{% endif %}
- Not defined
- {% endif %}
- |
- ||
Provider Network | ++ {{ termination.provider_network }} + | +||
Speed | @@ -92,21 +99,6 @@ {% endif %} | ||
IP Addressing | -
- {% if termination.connected_endpoint %}
- {% for ip in termination.ip_addresses %}
- {% if not forloop.first %} {% endif %} - {{ ip }} ({{ ip.vrf|default:"Global" }}) - {% empty %} - None - {% endfor %} - {% else %} - — - {% endif %} - |
- ||
Cross-Connect | {{ termination.xconnect_id|placeholder }} | diff --git a/netbox/templates/circuits/inc/speed_widget.html b/netbox/templates/circuits/inc/speed_widget.html deleted file mode 100644 index 988418945..000000000 --- a/netbox/templates/circuits/inc/speed_widget.html +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 8778c3ac2..718d7f65e 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -1,65 +1,16 @@ -{% extends 'base.html' %} -{% load buttons %} +{% extends 'generic/object.html' %} {% load static %} -{% load custom_links %} {% load helpers %} {% load plugins %} -{% block title %}{{ object }}{% endblock %} - -{% block header %} - - - -||
Circuits | - {{ circuits_table.rows|length }} + {{ circuits_table.rows|length }} |
Provider | ++ {{ object.provider }} + | +
Name | +{{ object.name }} | +
Description | +{{ object.description }} | +
{{ termination_a.device.site.region }}
+{{ termination_a.device.site.region|placeholder }}
+{{ termination_a.device.site.group|placeholder }}
{{ termination_a.device.site }}
{{ termination_a.device.location|placeholder }}
+{{ termination_a.device.rack|default:"None" }}
+{{ termination_a.device.rack|placeholder }}
Cable | diff --git a/netbox/templates/dcim/consoleserverport.html b/netbox/templates/dcim/consoleserverport.html index b64b4aff2..96dfa5761 100644 --- a/netbox/templates/dcim/consoleserverport.html +++ b/netbox/templates/dcim/consoleserverport.html @@ -2,6 +2,12 @@ {% load helpers %} {% load plugins %} +{% block breadcrumbs %} + {{ block.super }} +||
Type | -{{ object.get_type_display }} | +{{ object.get_type_display|placeholder }} | +
Speed | +{{ object.get_speed_display|placeholder }} | |
Description | @@ -34,6 +44,7 @@
Cable | diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 55be343ac..6c5d8588e 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -18,21 +18,41 @@
Site | +Region | {% if object.site.region %} - {{ object.site.region }} / + {% for region in object.site.region.get_ancestors %} + {{ region }} / + {% endfor %} + {{ object.site.region }} + {% else %} + None + {% endif %} + | +
Site | ++ {{ object.site }} + | +|
Location | ++ {% if object.location %} + {% for location in object.location.get_ancestors %} + {{ location }} / + {% endfor %} + {{ object.location }} + {% else %} + None {% endif %} - {{ object.site }} | |
Rack | {% if object.rack %} - {% if object.rack.group %} - {{ object.rack.group }} / - {% endif %} {{ object.rack }} {% else %} None @@ -74,7 +94,7 @@ | |
Device Type | - {{ object.device_type.display_name }} ({{ object.device_type.u_height }}U) + {{ object.device_type.display_name }} ({{ object.device_type.u_height }}U) | |
Role | - {{ object.device_role }} + {{ object.device_role }} | |
Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Color | ++ + | +
VM Role | ++ {% if object.vm_role %} + + {% else %} + + {% endif %} + | +
Devices | ++ {{ device_count }} + | +
Virtual Machines | ++ {% if object.vm_role %} + {{ virtualmachine_count }} + {% else %} + — + {% endif %} + | +
Cable | diff --git a/netbox/templates/dcim/inc/cabletermination.html b/netbox/templates/dcim/inc/cabletermination.html index 1962248e7..26a7e1cd3 100644 --- a/netbox/templates/dcim/inc/cabletermination.html +++ b/netbox/templates/dcim/inc/cabletermination.html @@ -1,12 +1,12 @@- {% if termination.parent.provider %} + {% if termination.parent_object.provider %} - - {{ termination.parent.provider }} - {{ termination.parent }} + + {{ termination.parent_object.provider }} + {{ termination.parent_object }} {% else %} - {{ termination.parent }} + {{ termination.parent_object }} {% endif %} |
diff --git a/netbox/templates/dcim/inc/device_napalm_tabs.html b/netbox/templates/dcim/inc/device_napalm_tabs.html
deleted file mode 100644
index f402d94ef..000000000
--- a/netbox/templates/dcim/inc/device_napalm_tabs.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% if not disabled_message %}
- | {{ endpoint.parent }} | +{{ endpoint.parent_object }} | {{ endpoint }} | {% endwith %} {% else %} diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 2c0f6e01f..d8069da43 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -3,6 +3,21 @@ {% load plugins %} {% load render_table from django_tables2 %} +{% block breadcrumbs %} + {{ block.super }} +
Management Only | ++ {% if object.mgmt_only %} + + {% else %} + + {% endif %} + | +||||
Parent | ++ {% if object.parent %} + {{ object.parent }} + {% else %} + None + {% endif %} + | +||||
LAG | - {% if object.lag%} + {% if object.lag %} {{ object.lag }} {% else %} None @@ -63,10 +98,11 @@ | ||||
802.1Q Mode | -{{ object.get_mode_display }} | +{{ object.get_mode_display|placeholder }} |
Cable | @@ -251,6 +291,11 @@ {% include 'panel_table.html' with table=vlan_table heading="VLANs" %} +
Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Site | +{{ object.site }} | +
Parent | ++ {% if object.parent %} + {{ object.parent }} + {% else %} + — + {% endif %} + | +
Racks | ++ {{ rack_count }} + | +
Devices | ++ {{ device_count }} + | +
Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Device types | ++ {{ devicetypes_table.rows|length }} + | +
Inventory Items | ++ {{ inventory_item_count }} + | +
Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Manufacturer | ++ {% if object.manufacturer %} + {{ object.manufacturer }} + {% else %} + None + {% endif %} + | +
NAPALM Driver | +{{ object.napalm_driver|placeholder }} | +
NAPALM Arguments | +{{ object.napalm_args }} |
+
Devices | ++ {{ devices_table.rows|length }} + | +
Cable | diff --git a/netbox/templates/dcim/powerfeed_edit.html b/netbox/templates/dcim/powerfeed_edit.html deleted file mode 100644 index 0a6581444..000000000 --- a/netbox/templates/dcim/powerfeed_edit.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends 'generic/object_edit.html' %} -{% load form_helpers %} - -{% block form %} -
Cable | diff --git a/netbox/templates/dcim/powerpanel.html b/netbox/templates/dcim/powerpanel.html index 179697b4f..9deb12005 100644 --- a/netbox/templates/dcim/powerpanel.html +++ b/netbox/templates/dcim/powerpanel.html @@ -1,59 +1,15 @@ -{% extends 'base.html' %} -{% load buttons %} -{% load custom_links %} +{% extends 'generic/object.html' %} {% load helpers %} -{% load static %} {% load plugins %} +{% load render_table from django_tables2 %} -{% block header %} - - -||
Rack Group | +Location |
- {% if object.rack_group %}
- {{ object.rack_group }}
+ {% if object.location %}
+ {{ object.location }}
{% else %}
None
{% endif %}
@@ -92,7 +48,37 @@
- {% include 'panel_table.html' with table=powerfeed_table heading='Connected Feeds' %}
+
{% plugin_full_width_page object %}
-
- {% if form.custom_fields %}
- Power Panel
-
- {% render_field form.region %}
- {% render_field form.site %}
- {% render_field form.rack_group %}
- {% render_field form.name %}
- {% render_field form.tags %}
-
-
-
- {% endif %}
-{% endblock %}
diff --git a/netbox/templates/dcim/powerport.html b/netbox/templates/dcim/powerport.html
index 7356bafb9..1a7cc24e7 100644
--- a/netbox/templates/dcim/powerport.html
+++ b/netbox/templates/dcim/powerport.html
@@ -2,6 +2,12 @@
{% load helpers %}
{% load plugins %}
+{% block breadcrumbs %}
+ {{ block.super }}
+ Custom Fields
-
- {% render_custom_fields form %}
-
-
@@ -42,6 +48,7 @@
|
Cable | diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 6a00308f3..94f0ea24c 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -1,74 +1,46 @@ -{% extends 'base.html' %} +{% extends 'generic/object.html' %} {% load buttons %} -{% load custom_links %} {% load helpers %} {% load static %} {% load plugins %} -{% block header %} - - -||||||||||
Group | +Location | - {% if object.group %} - {% for group in object.group.get_ancestors %} - {{ group }} / + {% if object.location %} + {% for location in object.location.get_ancestors %} + {{ location }} / {% endfor %} - {{ object.group }} + {{ object.location }} {% else %} None {% endif %} @@ -296,7 +268,7 @@ |
{{ resv.description }} - {{ resv.user }} · {{ resv.created }} + {{ resv.user }} · {{ resv.created|annotated_date }} |
|||||||
Group | +Location |
- {% if rack.group %}
- {{ rack.group }}
+ {% if rack.location %}
+ {{ rack.location }}
{% else %}
None
{% endif %}
diff --git a/netbox/templates/dcim/rackreservation_edit.html b/netbox/templates/dcim/rackreservation_edit.html
deleted file mode 100644
index 1465dc02d..000000000
--- a/netbox/templates/dcim/rackreservation_edit.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{% extends 'generic/object_edit.html' %}
-{% load form_helpers %}
-
-{% block form %}
-
-
- Rack Reservation
-
- {% render_field form.region %}
- {% render_field form.site %}
- {% render_field form.rack_group %}
- {% render_field form.rack %}
- {% render_field form.units %}
- {% render_field form.user %}
- {% render_field form.description %}
- {% render_field form.tags %}
-
-
-
- {% if form.custom_fields %}
- Tenant Assignment
-
- {% render_field form.tenant_group %}
- {% render_field form.tenant %}
-
-
-
- {% endif %}
-{% endblock %}
diff --git a/netbox/templates/dcim/rackrole.html b/netbox/templates/dcim/rackrole.html
new file mode 100644
index 000000000..89306b481
--- /dev/null
+++ b/netbox/templates/dcim/rackrole.html
@@ -0,0 +1,66 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+
+{% block breadcrumbs %}
+ Custom Fields
-
- {% render_custom_fields form %}
-
-
+
+
+
+
+
+ {% plugin_left_page object %}
+
+ Rack Role
+
+
+ {% include 'inc/custom_fields_panel.html' %}
+ {% plugin_right_page object %}
+
+
+
+{% endblock %}
diff --git a/netbox/templates/dcim/rearport.html b/netbox/templates/dcim/rearport.html
index 319850397..eb9452a0c 100644
--- a/netbox/templates/dcim/rearport.html
+++ b/netbox/templates/dcim/rearport.html
@@ -2,6 +2,12 @@
{% load helpers %}
{% load plugins %}
+{% block breadcrumbs %}
+ {{ block.super }}
+
+
+
+
+ {% include 'inc/paginator.html' with paginator=racks_table.paginator page=racks_table.page %}
+ {% plugin_full_width_page object %}
+
+ Racks
+
+ {% include 'inc/table.html' with table=racks_table %}
+ {% if perms.dcim.add_rack %}
+
+ {% endif %}
+
@@ -38,6 +44,7 @@
|
Cable | diff --git a/netbox/templates/dcim/region.html b/netbox/templates/dcim/region.html new file mode 100644 index 000000000..1e2d395dd --- /dev/null +++ b/netbox/templates/dcim/region.html @@ -0,0 +1,86 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Parent | ++ {% if object.parent %} + {{ object.parent }} + {% else %} + — + {% endif %} + | +
Sites | ++ {{ sites_table.rows|length }} + | +
Location | +Racks | +Devices | ++ | |
---|---|---|---|---|
{{ rg }} | -{{ rg.rack_count }} | ++ {{ location }} + | ++ {{ location.rack_count }} | ++ {{ location.device_count }} + |
All racks | -{{ stats.rack_count }} | - -
Name | +{{ object.name }} | +
Description | +{{ object.description|placeholder }} | +
Parent | ++ {% if object.parent %} + {{ object.parent }} + {% else %} + — + {% endif %} + | +
Sites | ++ {{ sites_table.rows|length }} + | +
Object | ++ {{ object.assigned_object }} + | +
Created | ++ {{ object.created|annotated_date }} + | +
Created By | ++ {{ object.created_by }} + | +
Kind | ++ {{ object.get_kind_display }} + | +
{{ object.object_data|render_json }}+ {% if object.prechange_data %} +
{% for k, v in object.prechange_data.items %}{% spaceless %}
+ {{ k }}: {{ v|render_json }}
+ {% endspaceless %}
+{% endfor %}
+ {% elif non_atomic_change %}
+ Warning: Comparing non-atomic change to previous change record ({{ prev_change.pk }})
+ {% else %}
+ None
+ {% endif %}
+ {% for k, v in object.postchange_data.items %}{% spaceless %}
+ {{ k }}: {{ v|render_json }}
+ {% endspaceless %}
+{% endfor %}
+ {% else %}
+ None
+ {% endif %}
{{ report.description }}
+{{ report.description|render_markdown }}
{% endif %} {% endblock %} @@ -38,7 +38,7 @@- Run: {{ result.created }} + Run: {{ result.created|annotated_date }} {% if result.completed %} Duration: {{ result.duration }} {% else %} @@ -66,9 +66,11 @@ {{ obj }} {% elif obj %} {{ obj }} + {% else %} + — {% endif %}
{{ script.Meta.description }}
+{{ script.Meta.description|render_markdown }}
- Created {{ object.created }} · Updated {{ object.last_updated|timesince }} ago -
diff --git a/netbox/templates/inc/custom_fields_panel.html b/netbox/templates/inc/custom_fields_panel.html index d5f858f15..bd80974eb 100644 --- a/netbox/templates/inc/custom_fields_panel.html +++ b/netbox/templates/inc/custom_fields_panel.html @@ -15,6 +15,8 @@ {% elif field.type == 'url' and value %} {{ value|truncatechars:70 }} + {% elif field.type == 'multiselect' and value %} + {{ value|join:", " }} {% elif value is not None %} {{ value }} {% elif field.required %} diff --git a/netbox/templates/inc/image_attachments.html b/netbox/templates/inc/image_attachments.html index 38be9924d..d1f4e45dc 100644 --- a/netbox/templates/inc/image_attachments.html +++ b/netbox/templates/inc/image_attachments.html @@ -1,3 +1,4 @@ +{% load helpers %} {% if images %}{{ attachment.size|filesizeformat }} | -{{ attachment.created }} | +{{ attachment.created|annotated_date }} |