mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Merge pull request #8586 from netbox-community/template-cleanup
Closes #8585: Support generic templates for plugins
This commit is contained in:
commit
45e5c4eb46
195
docs/plugins/development/templates.md
Normal file
195
docs/plugins/development/templates.md
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
# Templates
|
||||||
|
|
||||||
|
## Base Templates
|
||||||
|
|
||||||
|
The following template blocks are available on all templates.
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|--------------|----------|---------------------------------------------------------------------|
|
||||||
|
| `title` | Yes | Page title |
|
||||||
|
| `content` | Yes | Page content |
|
||||||
|
| `head` | - | Content to include in the HTML `<head>` element |
|
||||||
|
| `javascript` | - | Javascript content included at the end of the HTML `<body>` element |
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
For more information on how template blocks work, consult the [Django documentation](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#block).
|
||||||
|
|
||||||
|
### layout.html
|
||||||
|
|
||||||
|
Path: `base/layout.html`
|
||||||
|
|
||||||
|
NetBox provides a base template to ensure a consistent user experience, which plugins can extend with their own content. This is a general-purpose template that can be used when none of the function-specific templates below are suitable.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|-----------|----------|----------------------------|
|
||||||
|
| `header` | - | Page header |
|
||||||
|
| `tabs` | - | Horizontal navigation tabs |
|
||||||
|
| `modals` | - | Bootstrap 5 modal elements |
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
An example of a plugin template which extends `layout.html` is included below.
|
||||||
|
|
||||||
|
```jinja2
|
||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>My Custom Header</h1>
|
||||||
|
{% endblock header %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>{{ some_plugin_context_var }}</p>
|
||||||
|
{% endblock content %}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line of the template instructs Django to extend the NetBox base template and inject our custom content within its `content` block.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Django renders templates with its own custom [template language](https://docs.djangoproject.com/en/stable/topics/templates/#the-django-template-language). This is very similar to Jinja2, however there are some important distinctions of which authors should be aware. Be sure to familiarize yourself with Django's template language before attempting to create new templates.
|
||||||
|
|
||||||
|
## Generic View Templates
|
||||||
|
|
||||||
|
### object.html
|
||||||
|
|
||||||
|
Path: `generic/object.html`
|
||||||
|
|
||||||
|
This template is used by the `ObjectView` generic view to display a single object.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|---------------------|----------|----------------------------------------------|
|
||||||
|
| `breadcrumbs` | - | Breadcrumb list items (HTML `<li>` elements) |
|
||||||
|
| `object_identifier` | - | A unique identifier (string) for the object |
|
||||||
|
| `extra_controls` | - | Additional action buttons to display |
|
||||||
|
| `extra_tabs` | - | Additional tabs to include |
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|----------|----------|----------------------------------|
|
||||||
|
| `object` | Yes | The object instance being viewed |
|
||||||
|
|
||||||
|
### object_edit.html
|
||||||
|
|
||||||
|
Path: `generic/object_edit.html`
|
||||||
|
|
||||||
|
This template is used by the `ObjectEditView` generic view to create or modify a single object.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|------------------|----------|-------------------------------------------------------|
|
||||||
|
| `form` | - | Custom form content (within the HTML `<form>` element |
|
||||||
|
| `buttons` | - | Form submission buttons |
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|--------------|----------|-----------------------------------------------------------------|
|
||||||
|
| `object` | Yes | The object instance being modified (or none, if creating) |
|
||||||
|
| `form` | Yes | The form class for creating/modifying the object |
|
||||||
|
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
|
||||||
|
|
||||||
|
### object_delete.html
|
||||||
|
|
||||||
|
Path: `generic/object_delete.html`
|
||||||
|
|
||||||
|
This template is used by the `ObjectDeleteView` generic view to delete a single object.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|--------------|----------|-----------------------------------------------------------------|
|
||||||
|
| `object` | Yes | The object instance being deleted |
|
||||||
|
| `form` | Yes | The form class for confirming the object's deletion |
|
||||||
|
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
|
||||||
|
|
||||||
|
### object_list.html
|
||||||
|
|
||||||
|
Path: `generic/object_list.html`
|
||||||
|
|
||||||
|
This template is used by the `ObjectListView` generic view to display a filterable list of multiple objects.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|------------------|----------|--------------------------------------------------------------------|
|
||||||
|
| `extra_controls` | - | Additional action buttons |
|
||||||
|
| `bulk_buttons` | - | Additional bulk action buttons to display beneath the objects list |
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|------------------|----------|-----------------------------------------------------------------------|
|
||||||
|
| `model` | Yes | The object class |
|
||||||
|
| `table` | Yes | The table class used for rendering the list of objects |
|
||||||
|
| `permissions` | Yes | A mapping of add, change, and delete permissions for the current user |
|
||||||
|
| `action_buttons` | Yes | A list of buttons to display (options are `add`, `import`, `export`) |
|
||||||
|
| `filter_form` | - | The bound filterset form for filtering the objects list |
|
||||||
|
| `return_url` | - | The return URL to pass when submitting a bulk operation form |
|
||||||
|
|
||||||
|
### bulk_import.html
|
||||||
|
|
||||||
|
Path: `generic/bulk_import.html`
|
||||||
|
|
||||||
|
This template is used by the `BulkImportView` generic view to import multiple objects at once from CSV data.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|--------------|----------|--------------------------------------------------------------|
|
||||||
|
| `model` | Yes | The object class |
|
||||||
|
| `form` | Yes | The CSV import form class |
|
||||||
|
| `return_url` | - | The return URL to pass when submitting a bulk operation form |
|
||||||
|
| `fields` | - | A dictionary of form fields, to display import options |
|
||||||
|
|
||||||
|
### bulk_edit.html
|
||||||
|
|
||||||
|
Path: `generic/bulk_edit.html`
|
||||||
|
|
||||||
|
This template is used by the `BulkEditView` generic view to modify multiple objects simultaneously.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|--------------|----------|-----------------------------------------------------------------|
|
||||||
|
| `model` | Yes | The object class |
|
||||||
|
| `form` | Yes | The bulk edit form class |
|
||||||
|
| `table` | Yes | The table class used for rendering the list of objects |
|
||||||
|
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
|
||||||
|
|
||||||
|
### bulk_delete.html
|
||||||
|
|
||||||
|
Path: `generic/bulk_delete.html`
|
||||||
|
|
||||||
|
This template is used by the `BulkDeleteView` generic view to delete multiple objects simultaneously.
|
||||||
|
|
||||||
|
#### Blocks
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|-----------------|----------|---------------------------------------|
|
||||||
|
| `message_extra` | - | Supplementary warning message content |
|
||||||
|
|
||||||
|
#### Context
|
||||||
|
|
||||||
|
| Name | Required | Description |
|
||||||
|
|--------------|----------|-----------------------------------------------------------------|
|
||||||
|
| `model` | Yes | The object class |
|
||||||
|
| `form` | Yes | The bulk delete form class |
|
||||||
|
| `table` | Yes | The table class used for rendering the list of objects |
|
||||||
|
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
|
@ -71,47 +71,7 @@ A URL pattern has three components:
|
|||||||
|
|
||||||
This makes our view accessible at the URL `/plugins/animal-sounds/random/`. (Remember, our `AnimalSoundsConfig` class sets our plugin's base URL to `animal-sounds`.) Viewing this URL should show the base NetBox template with our custom content inside it.
|
This makes our view accessible at the URL `/plugins/animal-sounds/random/`. (Remember, our `AnimalSoundsConfig` class sets our plugin's base URL to `animal-sounds`.) Viewing this URL should show the base NetBox template with our custom content inside it.
|
||||||
|
|
||||||
## Templates
|
## Extending Core Views
|
||||||
|
|
||||||
### Plugin Views
|
|
||||||
|
|
||||||
NetBox provides a base template to ensure a consistent user experience, which plugins can extend with their own content. This template includes four content blocks:
|
|
||||||
|
|
||||||
* `title` - The page title
|
|
||||||
* `header` - The upper portion of the page
|
|
||||||
* `content` - The main page body
|
|
||||||
* `javascript` - A section at the end of the page for including Javascript code
|
|
||||||
|
|
||||||
For more information on how template blocks work, consult the [Django documentation](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#block).
|
|
||||||
|
|
||||||
```jinja2
|
|
||||||
{% extends 'base/layout.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with config=settings.PLUGINS_CONFIG.netbox_animal_sounds %}
|
|
||||||
<h2 class="text-center" style="margin-top: 200px">
|
|
||||||
{% if animal %}
|
|
||||||
The {{ animal.name|lower }} says
|
|
||||||
{% if config.loud %}
|
|
||||||
{{ animal.sound|upper }}!
|
|
||||||
{% else %}
|
|
||||||
{{ animal.sound }}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
No animals have been created yet!
|
|
||||||
{% endif %}
|
|
||||||
</h2>
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The first line of the template instructs Django to extend the NetBox base template and inject our custom content within its `content` block.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
Django renders templates with its own custom [template language](https://docs.djangoproject.com/en/stable/topics/templates/#the-django-template-language). This is very similar to Jinja2, however there are some important differences to be aware of.
|
|
||||||
|
|
||||||
### Extending Core Views
|
|
||||||
|
|
||||||
Plugins can inject custom content into certain areas of the detail views of applicable models. This is accomplished by subclassing `PluginTemplateExtension`, designating a particular NetBox model, and defining the desired methods to render custom content. Four methods are available:
|
Plugins can inject custom content into certain areas of the detail views of applicable models. This is accomplished by subclassing `PluginTemplateExtension`, designating a particular NetBox model, and defining the desired methods to render custom content. Four methods are available:
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ nav:
|
|||||||
- Getting Started: 'plugins/development/index.md'
|
- Getting Started: 'plugins/development/index.md'
|
||||||
- Models: 'plugins/development/models.md'
|
- Models: 'plugins/development/models.md'
|
||||||
- Views: 'plugins/development/views.md'
|
- Views: 'plugins/development/views.md'
|
||||||
|
- Templates: 'plugins/development/templates.md'
|
||||||
- Tables: 'plugins/development/tables.md'
|
- Tables: 'plugins/development/tables.md'
|
||||||
- Forms: 'plugins/development/forms.md'
|
- Forms: 'plugins/development/forms.md'
|
||||||
- Filter Sets: 'plugins/development/filtersets.md'
|
- Filter Sets: 'plugins/development/filtersets.md'
|
||||||
|
@ -803,7 +803,6 @@ class DeviceTypeView(generic.ObjectView):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'instance_count': instance_count,
|
'instance_count': instance_count,
|
||||||
'active_tab': 'devicetype',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -953,11 +952,10 @@ class ModuleTypeView(generic.ObjectView):
|
|||||||
queryset = ModuleType.objects.prefetch_related('manufacturer')
|
queryset = ModuleType.objects.prefetch_related('manufacturer')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
# instance_count = Module.objects.restrict(request.user).filter(device_type=instance).count()
|
instance_count = Module.objects.restrict(request.user).filter(module_type=instance).count()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
# 'instance_count': instance_count,
|
'instance_count': instance_count,
|
||||||
'active_tab': 'moduletype',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1570,7 +1568,6 @@ class DeviceView(generic.ObjectView):
|
|||||||
return {
|
return {
|
||||||
'services': services,
|
'services': services,
|
||||||
'vc_members': vc_members,
|
'vc_members': vc_members,
|
||||||
'active_tab': 'device',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.exceptions import FieldDoesNotExist, ValidationError
|
from django.core.exceptions import FieldDoesNotExist, ValidationError
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import transaction, IntegrityError
|
||||||
from django.db.models import ManyToManyField, ProtectedError
|
from django.db.models import ManyToManyField, ProtectedError
|
||||||
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
|
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django_tables2.export import TableExport
|
from django_tables2.export import TableExport
|
||||||
|
|
||||||
from extras.models import ExportTemplate
|
from extras.models import ExportTemplate
|
||||||
from extras.signals import clear_webhooks
|
from extras.signals import clear_webhooks
|
||||||
|
from netbox.tables import configure_table
|
||||||
from utilities.error_handlers import handle_protectederror
|
from utilities.error_handlers import handle_protectederror
|
||||||
from utilities.exceptions import PermissionsViolation
|
from utilities.exceptions import PermissionsViolation
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
@ -21,7 +22,6 @@ from utilities.forms import (
|
|||||||
)
|
)
|
||||||
from utilities.htmx import is_htmx
|
from utilities.htmx import is_htmx
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
from netbox.tables import configure_table
|
|
||||||
from utilities.views import GetReturnURLMixin
|
from utilities.views import GetReturnURLMixin
|
||||||
from .base import BaseMultiObjectView
|
from .base import BaseMultiObjectView
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class ObjectListView(BaseMultiObjectView):
|
|||||||
})
|
})
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'content_type': content_type,
|
'model': model,
|
||||||
'table': table,
|
'table': table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'action_buttons': self.action_buttons,
|
'action_buttons': self.action_buttons,
|
||||||
@ -304,7 +304,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
Attributes:
|
Attributes:
|
||||||
model_form: The form used to create each imported object
|
model_form: The form used to create each imported object
|
||||||
"""
|
"""
|
||||||
template_name = 'generic/object_bulk_import.html'
|
template_name = 'generic/bulk_import.html'
|
||||||
model_form = None
|
model_form = None
|
||||||
|
|
||||||
def _import_form(self, *args, **kwargs):
|
def _import_form(self, *args, **kwargs):
|
||||||
@ -369,9 +369,9 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
|
'model': self.model_form._meta.model,
|
||||||
'form': self._import_form(),
|
'form': self._import_form(),
|
||||||
'fields': self.model_form().fields,
|
'fields': self.model_form().fields,
|
||||||
'obj_type': self.model_form._meta.model._meta.verbose_name,
|
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
**self.get_extra_context(request),
|
**self.get_extra_context(request),
|
||||||
})
|
})
|
||||||
@ -418,9 +418,9 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
logger.debug("Form validation failed")
|
logger.debug("Form validation failed")
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
|
'model': self.model_form._meta.model,
|
||||||
'form': form,
|
'form': form,
|
||||||
'fields': self.model_form().fields,
|
'fields': self.model_form().fields,
|
||||||
'obj_type': self.model_form._meta.model._meta.verbose_name,
|
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
**self.get_extra_context(request),
|
**self.get_extra_context(request),
|
||||||
})
|
})
|
||||||
@ -434,7 +434,7 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
filterset: FilterSet to apply when deleting by QuerySet
|
filterset: FilterSet to apply when deleting by QuerySet
|
||||||
form: The form class used to edit objects in bulk
|
form: The form class used to edit objects in bulk
|
||||||
"""
|
"""
|
||||||
template_name = 'generic/object_bulk_edit.html'
|
template_name = 'generic/bulk_edit.html'
|
||||||
filterset = None
|
filterset = None
|
||||||
form = None
|
form = None
|
||||||
|
|
||||||
@ -590,7 +590,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
"""
|
"""
|
||||||
An extendable view for renaming objects in bulk.
|
An extendable view for renaming objects in bulk.
|
||||||
"""
|
"""
|
||||||
template_name = 'generic/object_bulk_rename.html'
|
template_name = 'generic/bulk_rename.html'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -681,7 +681,7 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
filterset: FilterSet to apply when deleting by QuerySet
|
filterset: FilterSet to apply when deleting by QuerySet
|
||||||
table: The table used to display devices being deleted
|
table: The table used to display devices being deleted
|
||||||
"""
|
"""
|
||||||
template_name = 'generic/object_bulk_delete.html'
|
template_name = 'generic/bulk_delete.html'
|
||||||
filterset = None
|
filterset = None
|
||||||
table = None
|
table = None
|
||||||
|
|
||||||
@ -759,8 +759,8 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
return redirect(self.get_return_url(request))
|
return redirect(self.get_return_url(request))
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
|
'model': model,
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type_plural': model._meta.verbose_name_plural,
|
|
||||||
'table': table,
|
'table': table,
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
**self.get_extra_context(request),
|
**self.get_extra_context(request),
|
||||||
@ -775,7 +775,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
"""
|
"""
|
||||||
Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
|
Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
|
||||||
"""
|
"""
|
||||||
template_name = 'generic/object_bulk_add_component.html'
|
template_name = 'generic/bulk_add_component.html'
|
||||||
parent_model = None
|
parent_model = None
|
||||||
parent_field = None
|
parent_field = None
|
||||||
form = None
|
form = None
|
||||||
|
@ -344,8 +344,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
|||||||
restrict_form_fields(form, request.user)
|
restrict_form_fields(form, request.user)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'object': obj,
|
||||||
'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': self.get_return_url(request, obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
**self.get_extra_context(request, obj),
|
**self.get_extra_context(request, obj),
|
||||||
@ -423,8 +422,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
|||||||
logger.debug("Form validation failed")
|
logger.debug("Form validation failed")
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'object': obj,
|
||||||
'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': self.get_return_url(request, obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
**self.get_extra_context(request, obj),
|
**self.get_extra_context(request, obj),
|
||||||
@ -468,7 +466,6 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'object': obj,
|
'object': obj,
|
||||||
'object_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': self.get_return_url(request, obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
**self.get_extra_context(request, obj),
|
**self.get_extra_context(request, obj),
|
||||||
@ -513,7 +510,6 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'object': obj,
|
'object': obj,
|
||||||
'object_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': self.get_return_url(request, obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
**self.get_extra_context(request, obj),
|
**self.get_extra_context(request, obj),
|
||||||
@ -557,8 +553,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
instance = self.alter_object(self.queryset.model, request)
|
instance = self.alter_object(self.queryset.model, request)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': instance,
|
'object': instance,
|
||||||
'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'replication_form': form,
|
'replication_form': form,
|
||||||
'form': model_form,
|
'form': model_form,
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
@ -577,8 +572,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
return redirect(self.get_return_url(request))
|
return redirect(self.get_return_url(request))
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': instance,
|
'object': instance,
|
||||||
'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
'replication_form': form,
|
'replication_form': form,
|
||||||
'form': model_form,
|
'form': model_form,
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
|
@ -4,6 +4,14 @@
|
|||||||
{% load search %}
|
{% load search %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
Blocks:
|
||||||
|
header: Page header
|
||||||
|
tabs: Horizontal navigation tabs
|
||||||
|
content: Page content
|
||||||
|
modals: Bootstrap 5 modal components
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
{% block layout %}
|
{% block layout %}
|
||||||
|
|
||||||
<div class="container-fluid px-0">
|
<div class="container-fluid px-0">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %}
|
{% block title %}{{ object.circuit.provider }} {{ object.circuit }} - Side {{ form.term_side.value }}{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<div class="field-group my-5">
|
<div class="field-group my-5">
|
||||||
@ -12,13 +12,13 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">Provider</label>
|
<label class="col-sm-3 col-form-label text-lg-end">Provider</label>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input class="form-control" value="{{ obj.circuit.provider }}" disabled />
|
<input class="form-control" value="{{ object.circuit.provider }}" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">Circuit</label>
|
<label class="col-sm-3 col-form-label text-lg-end">Circuit</label>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input class="form-control" value="{{ obj.circuit.cid }}" disabled />
|
<input class="form-control" value="{{ object.circuit.cid }}" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@ -69,7 +69,7 @@
|
|||||||
{# Override buttons block, 'Create & Add Another'/'_addanother' is not needed on a circuit. #}
|
{# Override buttons block, 'Create & Add Another'/'_addanother' is not needed on a circuit. #}
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
|
<a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
|
||||||
{% if obj.pk %}
|
{% if object.pk %}
|
||||||
<button type="submit" name="_update" class="btn btn-primary">
|
<button type="submit" name="_update" class="btn btn-primary">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
|
@ -95,13 +95,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tab_items %}
|
{% block extra_tabs %}
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<a href="{% url 'dcim:device' pk=object.pk %}" class="nav-link{% if active_tab == 'device' %} active{% endif %}">
|
|
||||||
Device
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% with tab_name='device-bays' devicebay_count=object.devicebays.count %}
|
{% with tab_name='device-bays' devicebay_count=object.devicebays.count %}
|
||||||
{% if active_tab == tab_name or devicebay_count %}
|
{% if active_tab == tab_name or devicebay_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
{% extends 'generic/object_edit.html' %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form_fields %}
|
{% block form %}
|
||||||
|
<div class="field-group mb-5">
|
||||||
{% if form.instance.device %}
|
{% if form.instance.device %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">Device</label>
|
<label class="col-sm-3 col-form-label text-lg-end">Device</label>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input class="form-control" value="{{ form.instance.device }}" disabled />
|
<input class="form-control" value="{{ form.instance.device }}" disabled />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% render_form form %}
|
{% render_form form %}
|
||||||
{% endblock %}
|
</div>
|
||||||
|
{% endblock form %}
|
||||||
|
@ -34,19 +34,19 @@
|
|||||||
{% render_field form.location %}
|
{% render_field form.location %}
|
||||||
{% render_field form.rack %}
|
{% render_field form.rack %}
|
||||||
|
|
||||||
{% if obj.device_type.is_child_device and obj.parent_bay %}
|
{% if object.device_type.is_child_device and object.parent_bay %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-3 col-form-label">Parent Device</label>
|
<label class="col-sm-3 col-form-label">Parent Device</label>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input class="form-control" value="{{ obj.parent_bay.device }}" disabled />
|
<input class="form-control" value="{{ object.parent_bay.device }}" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-3 col-form-label">Parent Bay</label>
|
<label class="col-sm-3 col-form-label">Parent Bay</label>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input class="form-control" value="{{ obj.parent_bay.name }}" disabled />
|
<input class="form-control" value="{{ object.parent_bay.name }}" disabled />
|
||||||
<a href="{% url 'dcim:devicebay_depopulate' pk=obj.parent_bay.pk %}" title="Regenerate Slug" class="btn btn-danger d-inline-flex align-items-center">
|
<a href="{% url 'dcim:devicebay_depopulate' pk=object.parent_bay.pk %}" title="Regenerate Slug" class="btn btn-danger d-inline-flex align-items-center">
|
||||||
<i class="mdi mdi-close-thick"></i> Remove
|
<i class="mdi mdi-close-thick"></i> Remove
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% render_field form.status %}
|
{% render_field form.status %}
|
||||||
{% render_field form.platform %}
|
{% render_field form.platform %}
|
||||||
{% if obj.pk %}
|
{% if object.pk %}
|
||||||
{% render_field form.primary_ip4 %}
|
{% render_field form.primary_ip4 %}
|
||||||
{% render_field form.primary_ip6 %}
|
{% render_field form.primary_ip6 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends 'generic/object_bulk_import.html' %}
|
{% extends 'generic/bulk_import.html' %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
{% include 'dcim/inc/device_import_header.html' %}
|
{% include 'dcim/inc/device_import_header.html' %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends 'generic/object_bulk_import.html' %}
|
{% extends 'generic/bulk_import.html' %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
{% include 'dcim/inc/device_import_header.html' with active_tab='child_import' %}
|
{% include 'dcim/inc/device_import_header.html' with active_tab='child_import' %}
|
||||||
|
@ -52,13 +52,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tab_items %}
|
{% block extra_tabs %}
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<a href="{% url 'dcim:devicetype' pk=object.pk %}" class="nav-link{% if active_tab == 'devicetype' %} active{% endif %}">
|
|
||||||
Device Type
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% with tab_name='device-bay-templates' devicebay_count=object.devicebaytemplates.count %}
|
{% with tab_name='device-bay-templates' devicebay_count=object.devicebaytemplates.count %}
|
||||||
{% if active_tab == tab_name or devicebay_count %}
|
{% if active_tab == tab_name or devicebay_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
|
@ -93,8 +93,8 @@
|
|||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
||||||
{% if obj.pk %}
|
{% if object.pk %}
|
||||||
<button type="button" return-url="?return_url={% url 'dcim:interface_edit' pk=obj.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
|
<button type="button" return-url="?return_url={% url 'dcim:interface_edit' pk=object.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
|
||||||
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="submit" name="_addanother" class="btn btn-outline-primary">Create & Add Another</button>
|
<button type="submit" name="_addanother" class="btn btn-outline-primary">Create & Add Another</button>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends 'generic/object_bulk_delete.html' %}
|
{% extends 'generic/bulk_delete.html' %}
|
||||||
|
|
||||||
{% block message_extra %}
|
{% block message_extra %}
|
||||||
<p class="text-center text-danger"><i class="mdi mdi-alert"></i> This will also delete all child inventory items of those listed.</p>
|
<p class="text-center text-danger"><i class="mdi mdi-alert"></i> This will also delete all child inventory items of those listed.</p>
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
{% render_form replication_form %}
|
{% render_form replication_form %}
|
||||||
{% if obj.component %}
|
{% if object.component %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">
|
<label class="col-sm-3 col-form-label text-lg-end">
|
||||||
{{ obj.component|meta:"verbose_name"|bettertitle }}
|
{{ object.component|meta:"verbose_name"|bettertitle }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input class="form-control" value="{{ obj.component }}" disabled />
|
<input class="form-control" value="{{ object.component }}" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -22,12 +22,10 @@
|
|||||||
<td>Part Number</td>
|
<td>Part Number</td>
|
||||||
<td>{{ object.part_number|placeholder }}</td>
|
<td>{{ object.part_number|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% comment %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Instances</td>
|
<td>Instances</td>
|
||||||
<td><a href="{% url 'dcim:module_list' %}?module_type_id={{ object.pk }}">{{ instance_count }}</a></td>
|
<td><a href="{% url 'dcim:module_list' %}?module_type_id={{ object.pk }}">{{ instance_count }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endcomment %}
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,13 +43,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tab_items %}
|
{% block extra_tabs %}
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<a href="{% url 'dcim:moduletype' pk=object.pk %}" class="nav-link{% if active_tab == 'moduletype' %} active{% endif %}">
|
|
||||||
Module Type
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% with interface_count=object.interfacetemplates.count %}
|
{% with interface_count=object.interfacetemplates.count %}
|
||||||
{% if interface_count %}
|
{% if interface_count %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
{% extends 'generic/object_edit.html' %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block form_fields %}
|
{% block form %}
|
||||||
<div class="row mb-3">
|
<div class="field-group mb-5">
|
||||||
<label class="col-sm-3 col-form-label text-lg-end required">
|
<div class="row mb-3">
|
||||||
{{ obj.parent|meta:"verbose_name"|bettertitle }}
|
<label class="col-sm-3 col-form-label text-lg-end required">
|
||||||
</label>
|
{{ object.parent|meta:"verbose_name"|bettertitle }}
|
||||||
<div class="col-sm-9">
|
</label>
|
||||||
<div class="form-control-plaintext">
|
<div class="col-sm-9">
|
||||||
<a href="{{ obj.parent.get_absolute_url }}" class="">{{ obj.parent }}</a>
|
<div class="form-control-plaintext">
|
||||||
|
<a href="{{ object.parent.get_absolute_url }}" class="">{{ object.parent }}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% render_form form %}
|
||||||
</div>
|
</div>
|
||||||
{{ block.super }}
|
{% endblock form %}
|
||||||
{% endblock form_fields %}
|
|
||||||
|
45
netbox/templates/generic/bulk_delete.html
Normal file
45
netbox/templates/generic/bulk_delete.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
Blocks:
|
||||||
|
message_extra: Supplementary warning message content
|
||||||
|
|
||||||
|
Context:
|
||||||
|
model: The model class of the objects being deleted
|
||||||
|
form: The bulk delete form class
|
||||||
|
table: The table class for rendering list of objects being deleted
|
||||||
|
return_url: The URL to which the user is redirected after submitting the form
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block title %}Delete {{ table.rows|length }} {{ model|meta:"verbose_name_plural"|bettertitle }}?{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-md px-0">
|
||||||
|
<div class="alert alert-danger mb-3" role="alert">
|
||||||
|
<h4 class="alert-heading">Confirm Bulk Deletion</h4>
|
||||||
|
<hr />
|
||||||
|
<strong>Warning:</strong> The following operation will delete <strong>{{ table.rows|length }}</strong>
|
||||||
|
{{ model|meta:"verbose_name_plural" }}. Please carefully review the objects to be deleted and confirm below.
|
||||||
|
{% block message_extra %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-xl px-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
{% render_table table 'inc/table.html' %}
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form.hidden_fields %}
|
||||||
|
{{ field }}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="text-end">
|
||||||
|
<a href="{{ return_url }}" class="btn btn-outline-dark">Cancel</a>
|
||||||
|
<button type="submit" name="_confirm" class="btn btn-danger">Delete {{ table.rows|length }} {{ model|meta:"verbose_name_plural" }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -3,7 +3,15 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block title %}Editing {{ table.rows|length }} {{ model|meta:"verbose_name_plural" }}{% endblock %}
|
{% comment %}
|
||||||
|
Context:
|
||||||
|
model: The model class of the objects being modified
|
||||||
|
form: The bulk edit form class
|
||||||
|
table: The table class for rendering list of objects being modified
|
||||||
|
return_url: The URL to which the user is redirected after submitting the form
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block title %}Editing {{ table.rows|length }} {{ model|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
@ -19,7 +27,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock tabs %}
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
@ -128,6 +136,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock content-wrapper %}
|
@ -2,7 +2,15 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}{{ obj_type|bettertitle }} Bulk Import{% endblock %}
|
{% comment %}
|
||||||
|
Context:
|
||||||
|
model: The model class being imported
|
||||||
|
form: The bulk import form
|
||||||
|
fields: A dictionary of form fields, to display import options (optional)
|
||||||
|
return_url: The URL to which the user is redirected after submitting the form
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block title %}{{ model|meta:"verbose_name"|bettertitle }} Bulk Import{% endblock %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
@ -10,7 +18,7 @@
|
|||||||
<a class ="nav-link active" href="#">Bulk Import</a>
|
<a class ="nav-link active" href="#">Bulk Import</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock tabs %}
|
||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
@ -145,4 +153,4 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock content-wrapper %}
|
@ -5,6 +5,18 @@
|
|||||||
{% load perms %}
|
{% load perms %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
Blocks:
|
||||||
|
breadcrumbs: Breadcrumb list items (HTML <li> elements)
|
||||||
|
object_identifier: Unique identifier for the object
|
||||||
|
extra_controls: Additional action buttons to display
|
||||||
|
extra_tabs: Additional tabs to include
|
||||||
|
content: Page content
|
||||||
|
|
||||||
|
Context:
|
||||||
|
object: The object instance being viewed
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
{# Breadcrumbs #}
|
{# Breadcrumbs #}
|
||||||
@ -66,11 +78,15 @@
|
|||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
{% block tab_items %}
|
{# Primary tab #}
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock tab_items %}
|
|
||||||
|
{# Include any additional tabs #}
|
||||||
|
{% block extra_tabs %}{% endblock %}
|
||||||
|
|
||||||
|
{# Object journal #}
|
||||||
{% if perms.extras.view_journalentry %}
|
{% if perms.extras.view_journalentry %}
|
||||||
{% with journal_viewname=object|viewname:'journal' %}
|
{% with journal_viewname=object|viewname:'journal' %}
|
||||||
{% url journal_viewname pk=object.pk as journal_url %}
|
{% url journal_viewname pk=object.pk as journal_url %}
|
||||||
@ -83,6 +99,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Object changelog #}
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
{% with changelog_viewname=object|viewname:'changelog' %}
|
{% with changelog_viewname=object|viewname:'changelog' %}
|
||||||
{% url changelog_viewname pk=object.pk as changelog_url %}
|
{% url changelog_viewname pk=object.pk as changelog_url %}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
{% extends 'base/layout.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
|
|
||||||
{% block title %}Delete {{ table.rows|length }} {{ obj_type_plural|bettertitle }}?{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container-md px-0">
|
|
||||||
<div class="alert alert-danger mb-3" role="alert">
|
|
||||||
<h4 class="alert-heading">Confirm Bulk Deletion</h4>
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<strong>Warning:</strong> The following operation will delete <strong>{{ table.rows|length }}</strong> {{ obj_type_plural }}. Please carefully review the {{ obj_type_plural }} to be deleted and confirm below.
|
|
||||||
</div>
|
|
||||||
{% block message_extra %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container-xl px-0">
|
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% for field in form.hidden_fields %}
|
|
||||||
{{ field }}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="text-end">
|
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-dark">Cancel</a>
|
|
||||||
<button type="submit" name="_confirm" class="btn btn-danger">Delete {{ table.rows|length }} {{ obj_type_plural }}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
|
@ -1,7 +1,16 @@
|
|||||||
{% extends 'base/layout.html' %}
|
{% extends 'base/layout.html' %}
|
||||||
|
{% load helpers %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}Delete {{ object_type }}?{% endblock %}
|
{% comment %}
|
||||||
|
Context:
|
||||||
|
object: Python instance of the object being deleted
|
||||||
|
form: The delete confirmation form
|
||||||
|
form_url: URL for form submission (optional; defaults to current path)
|
||||||
|
return_url: The URL to which the user is redirected after submitting the form
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block title %}Delete {{ object|meta:"verbose_name" }}?{% endblock %}
|
||||||
|
|
||||||
{% block header %}{% endblock %}
|
{% block header %}{% endblock %}
|
||||||
|
|
||||||
|
@ -2,15 +2,26 @@
|
|||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
Blocks:
|
||||||
|
form: Content within the <form> element
|
||||||
|
buttons: Form submission buttons
|
||||||
|
|
||||||
|
Context:
|
||||||
|
object: Python instance of the object being edited
|
||||||
|
form: The edit form
|
||||||
|
return_url: The URL to which the user is redirected after submitting the form
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}
|
{% if object.pk %}Editing {{ object|meta:"verbose_name" }} {{ object }}{% else %}Add a new {{ object|meta:"verbose_name" }}{% endif %}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
<button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
||||||
{% if obj.pk %}Edit{% else %}Create{% endif %}
|
{% if object.pk %}Edit{% else %}Create{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -21,9 +32,9 @@
|
|||||||
<div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
|
<div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
|
||||||
|
|
||||||
{# Link to model documentation #}
|
{# Link to model documentation #}
|
||||||
{% if obj and settings.DOCS_ROOT %}
|
{% if object and settings.DOCS_ROOT %}
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<a href="{{ obj|get_docs_url }}" target="_blank" class="btn btn-sm btn-outline-secondary" title="View model documentation">
|
<a href="{{ object|get_docs_url }}" target="_blank" class="btn btn-sm btn-outline-secondary" title="View model documentation">
|
||||||
<i class="mdi mdi-help-circle"></i> Help
|
<i class="mdi mdi-help-circle"></i> Help
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -75,7 +86,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{# Render all fields in a single group #}
|
{# Render all fields in a single group #}
|
||||||
<div class="field-group mb-5">
|
<div class="field-group mb-5">
|
||||||
{% block form_fields %}{% render_form form %}{% endblock %}
|
{% render_form form %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -84,7 +95,7 @@
|
|||||||
<div class="text-end my-3">
|
<div class="text-end my-3">
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
|
<a class="btn btn-outline-danger" href="{{ return_url }}">Cancel</a>
|
||||||
{% if obj.pk %}
|
{% if object.pk %}
|
||||||
<button type="submit" name="_update" class="btn btn-primary">
|
<button type="submit" name="_update" class="btn btn-primary">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
|
@ -4,20 +4,37 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
{% comment %}
|
||||||
|
Blocks:
|
||||||
|
extra_controls: Additional action buttons
|
||||||
|
bulk_buttons: Additional bulk action buttons to display beneath the objects
|
||||||
|
list
|
||||||
|
|
||||||
|
Context:
|
||||||
|
model: The model class being listed
|
||||||
|
table: The table class used for rendering the list of objects
|
||||||
|
permissions: A mapping of add/change/delete permissions to boolean indicating
|
||||||
|
whether the current user possesses each of them. Controls the display of
|
||||||
|
add/edit/delete buttons.
|
||||||
|
action_buttons: A list of buttons to display. Options are add, import, export.
|
||||||
|
filter_form: The bound filterset form for filtering the objects list (optional)
|
||||||
|
return_url: Return URL to use for bulk actions (optional)
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% block title %}{{ model|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
||||||
|
|
||||||
{% block controls %}
|
{% block controls %}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{% block extra_controls %}{% endblock %}
|
{% block extra_controls %}{% endblock %}
|
||||||
{% if permissions.add and 'add' in action_buttons %}
|
{% if permissions.add and 'add' in action_buttons %}
|
||||||
{% add_button content_type.model_class|validated_viewname:"add" %}
|
{% add_button model|validated_viewname:"add" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if permissions.add and 'import' in action_buttons %}
|
{% if permissions.add and 'import' in action_buttons %}
|
||||||
{% import_button content_type.model_class|validated_viewname:"import" %}
|
{% import_button model|validated_viewname:"import" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'export' in action_buttons %}
|
{% if 'export' in action_buttons %}
|
||||||
{% export_button content_type %}
|
{% export_button model|content_type %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -25,22 +42,20 @@
|
|||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
{% block tab_items %}
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
||||||
|
Records
|
||||||
|
{% badge table.page.paginator.count %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% if filter_form %}
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
|
||||||
Records
|
Filters
|
||||||
{% badge table.page.paginator.count %}
|
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% if filter_form %}
|
{% endif %}
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
|
|
||||||
Filters
|
|
||||||
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock tab_items %}
|
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock tabs %}
|
{% endblock tabs %}
|
||||||
|
|
||||||
@ -57,7 +72,7 @@
|
|||||||
|
|
||||||
{# "Select all" form #}
|
{# "Select all" form #}
|
||||||
{% if table.paginator.num_pages > 1 %}
|
{% if table.paginator.num_pages > 1 %}
|
||||||
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
|
{% with bulk_edit_url=model|validated_viewname:"bulk_edit" bulk_delete_url=model|validated_viewname:"bulk_delete" %}
|
||||||
<div id="select-all-box" class="d-none card noprint">
|
<div id="select-all-box" class="d-none card noprint">
|
||||||
<form method="post" class="form col-md-12">
|
<form method="post" class="form col-md-12">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -102,7 +117,7 @@
|
|||||||
|
|
||||||
{# Form buttons #}
|
{# Form buttons #}
|
||||||
{% if permissions.change or permissions.delete %}
|
{% if permissions.change or permissions.delete %}
|
||||||
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
|
{% with bulk_edit_url=model|validated_viewname:"bulk_edit" bulk_delete_url=model|validated_viewname:"bulk_delete" %}
|
||||||
<div class="noprint bulk-buttons">
|
<div class="noprint bulk-buttons">
|
||||||
<div class="bulk-button-group">
|
<div class="bulk-button-group">
|
||||||
{% block bulk_buttons %}{% endblock %}
|
{% block bulk_buttons %}{% endblock %}
|
||||||
|
@ -7,12 +7,7 @@
|
|||||||
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tab_items %}
|
{% block extra_tabs %}
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">
|
|
||||||
Aggregate
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% if perms.ipam.view_prefix %}
|
{% if perms.ipam.view_prefix %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:aggregate_prefixes' pk=object.pk %}">
|
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:aggregate_prefixes' pk=object.pk %}">
|
||||||
|
@ -9,12 +9,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tab_items %}
|
{% block extra_tabs %}
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">
|
|
||||||
IP Range
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% if perms.ipam.view_ipaddress %}
|
{% if perms.ipam.view_ipaddress %}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link{% if active_tab == 'ip-addresses' %} active{% endif %}" href="{% url 'ipam:iprange_ipaddresses' pk=object.pk %}">
|
<a class="nav-link{% if active_tab == 'ip-addresses' %} active{% endif %}" href="{% url 'ipam:iprange_ipaddresses' pk=object.pk %}">
|
||||||
|
@ -9,12 +9,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block tab_items %}
|
{% block extra_tabs %}
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{% url 'ipam:prefix' pk=object.pk %}">
|
|
||||||
Prefix
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:prefix_prefixes' pk=object.pk %}">
|
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:prefix_prefixes' pk=object.pk %}">
|
||||||
Child Prefixes {% badge object.get_child_prefixes.count %}
|
Child Prefixes {% badge object.get_child_prefixes.count %}
|
||||||
|
@ -58,8 +58,8 @@
|
|||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
||||||
{% if obj.pk %}
|
{% if object.pk %}
|
||||||
<button type="button" return-url="?return_url={% url 'virtualization:vminterface_edit' pk=obj.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
|
<button type="button" return-url="?return_url={% url 'virtualization:vminterface_edit' pk=object.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
|
||||||
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="submit" name="_addanother" class="btn btn-outline-primary">Create & Add Another</button>
|
<button type="submit" name="_addanother" class="btn btn-outline-primary">Create & Add Another</button>
|
||||||
|
@ -266,7 +266,7 @@ class ClusterAddDevicesView(generic.ObjectEditView):
|
|||||||
class ClusterRemoveDevicesView(generic.ObjectEditView):
|
class ClusterRemoveDevicesView(generic.ObjectEditView):
|
||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
form = forms.ClusterRemoveDevicesForm
|
form = forms.ClusterRemoveDevicesForm
|
||||||
template_name = 'generic/object_bulk_remove.html'
|
template_name = 'generic/bulk_remove.html'
|
||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user