mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-10 05:42:16 -06:00
Compare commits
11 Commits
fix-19669-
...
a3ee46a32a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3ee46a32a | ||
|
|
eb86b9d6b0 | ||
|
|
37fb8ae8ae | ||
|
|
6b2643c9cd | ||
|
|
9946ff910c | ||
|
|
e95c3825de | ||
|
|
92a13a4226 | ||
|
|
ae3de95dce | ||
|
|
a1cd81ff35 | ||
|
|
ce12de8b6d | ||
|
|
601a77ac73 |
@@ -158,6 +158,7 @@ LOGGING = {
|
|||||||
* `netbox.<app>.<model>` - Generic form for model-specific log messages
|
* `netbox.<app>.<model>` - Generic form for model-specific log messages
|
||||||
* `netbox.auth.*` - Authentication events
|
* `netbox.auth.*` - Authentication events
|
||||||
* `netbox.api.views.*` - Views which handle business logic for the REST API
|
* `netbox.api.views.*` - Views which handle business logic for the REST API
|
||||||
|
* `netbox.jobs.*` - Background jobs
|
||||||
* `netbox.reports.*` - Report execution (`module.name`)
|
* `netbox.reports.*` - Report execution (`module.name`)
|
||||||
* `netbox.scripts.*` - Custom script execution (`module.name`)
|
* `netbox.scripts.*` - Custom script execution (`module.name`)
|
||||||
* `netbox.views.*` - Views which handle business logic for the web UI
|
* `netbox.views.*` - Views which handle business logic for the web UI
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ The assignment of platforms to devices is an optional feature, and may be disreg
|
|||||||
|
|
||||||
### Name
|
### Name
|
||||||
|
|
||||||
A unique human-friendly name.
|
A human-friendly name for the platform. Must be unique per manufacturer.
|
||||||
|
|
||||||
### Slug
|
### Slug
|
||||||
|
|
||||||
A unique URL-friendly identifier. (This value can be used for filtering.)
|
A URL-friendly identifier; must be unique per manufacturer. (This value can be used for filtering.)
|
||||||
|
|
||||||
### Manufacturer
|
### Manufacturer
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,27 @@ You can schedule the background job from within your code (e.g. from a model's `
|
|||||||
|
|
||||||
This is the human-friendly names of your background job. If omitted, the class name will be used.
|
This is the human-friendly names of your background job. If omitted, the class name will be used.
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
!!! info "This feature was introduced in NetBox v4.4."
|
||||||
|
|
||||||
|
A Python logger is instantiated by the runner for each job. It can be utilized within a job's `run()` method as needed:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
obj = MyModel.objects.get(pk=kwargs.get('pk'))
|
||||||
|
self.logger.info("Retrieved object {obj}")
|
||||||
|
```
|
||||||
|
|
||||||
|
Four of the standard Python logging levels are supported:
|
||||||
|
|
||||||
|
* `debug()`
|
||||||
|
* `info()`
|
||||||
|
* `warning()`
|
||||||
|
* `error()`
|
||||||
|
|
||||||
|
Log entries recorded using the runner's logger will be saved in the job's log in the database in addition to being processed by other [system logging handlers](../../configuration/system.md#logging).
|
||||||
|
|
||||||
### Scheduled Jobs
|
### Scheduled Jobs
|
||||||
|
|
||||||
As described above, jobs can be scheduled for immediate execution or at any later time using the `enqueue()` method. However, for management purposes, the `enqueue_once()` method allows a job to be scheduled exactly once avoiding duplicates. If a job is already scheduled for a particular instance, a second one won't be scheduled, respecting thread safety. An example use case would be to schedule a periodic task that is bound to an instance in general, but not to any event of that instance (such as updates). The parameters of the `enqueue_once()` method are identical to those of `enqueue()`.
|
As described above, jobs can be scheduled for immediate execution or at any later time using the `enqueue()` method. However, for management purposes, the `enqueue_once()` method allows a job to be scheduled exactly once avoiding duplicates. If a job is already scheduled for a particular instance, a second one won't be scheduled, respecting thread safety. An example use case would be to schedule a periodic task that is bound to an instance in general, but not to any event of that instance (such as updates). The parameters of the `enqueue_once()` method are identical to those of `enqueue()`.
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ Generic view classes (documented below) facilitate common operations, such as cr
|
|||||||
| `ObjectListView` | View a list of objects |
|
| `ObjectListView` | View a list of objects |
|
||||||
| `BulkImportView` | Import a set of new objects |
|
| `BulkImportView` | Import a set of new objects |
|
||||||
| `BulkEditView` | Edit multiple objects |
|
| `BulkEditView` | Edit multiple objects |
|
||||||
|
| `BulkRenameView` | Rename multiple objects |
|
||||||
| `BulkDeleteView` | Delete multiple objects |
|
| `BulkDeleteView` | Delete multiple objects |
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
@@ -171,6 +172,10 @@ Below are the class definitions for NetBox's multi-object views. These views han
|
|||||||
options:
|
options:
|
||||||
members: false
|
members: false
|
||||||
|
|
||||||
|
::: netbox.views.generic.BulkRenameView
|
||||||
|
options:
|
||||||
|
members: false
|
||||||
|
|
||||||
::: netbox.views.generic.BulkDeleteView
|
::: netbox.views.generic.BulkDeleteView
|
||||||
options:
|
options:
|
||||||
members:
|
members:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from dcim.views import PathTraceView
|
from dcim.views import PathTraceView
|
||||||
from ipam.models import ASN
|
from ipam.models import ASN
|
||||||
|
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
@@ -79,6 +80,11 @@ class ProviderBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ProviderBulkEditForm
|
form = forms.ProviderBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Provider, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ProviderBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Provider.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Provider, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Provider, 'bulk_delete', path='delete', detail=False)
|
||||||
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Provider.objects.annotate(
|
queryset = Provider.objects.annotate(
|
||||||
@@ -141,6 +147,11 @@ class ProviderAccountBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ProviderAccountBulkEditForm
|
form = forms.ProviderAccountBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ProviderAccount, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ProviderAccountBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ProviderAccount.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ProviderAccount, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ProviderAccount, 'bulk_delete', path='delete', detail=False)
|
||||||
class ProviderAccountBulkDeleteView(generic.BulkDeleteView):
|
class ProviderAccountBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ProviderAccount.objects.annotate(
|
queryset = ProviderAccount.objects.annotate(
|
||||||
@@ -212,6 +223,11 @@ class ProviderNetworkBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ProviderNetworkBulkEditForm
|
form = forms.ProviderNetworkBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ProviderNetwork, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ProviderNetworkBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ProviderNetwork.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ProviderNetwork, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ProviderNetwork, 'bulk_delete', path='delete', detail=False)
|
||||||
class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
|
class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
@@ -271,6 +287,11 @@ class CircuitTypeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CircuitTypeBulkEditForm
|
form = forms.CircuitTypeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitType, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CircuitTypeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = CircuitType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CircuitType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(CircuitType, 'bulk_delete', path='delete', detail=False)
|
||||||
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CircuitType.objects.annotate(
|
queryset = CircuitType.objects.annotate(
|
||||||
@@ -337,6 +358,12 @@ class CircuitBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CircuitBulkEditForm
|
form = forms.CircuitBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Circuit, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CircuitBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Circuit.objects.all()
|
||||||
|
field_name = 'cid'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Circuit, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Circuit, 'bulk_delete', path='delete', detail=False)
|
||||||
class CircuitBulkDeleteView(generic.BulkDeleteView):
|
class CircuitBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Circuit.objects.prefetch_related(
|
queryset = Circuit.objects.prefetch_related(
|
||||||
@@ -432,6 +459,7 @@ class CircuitTerminationListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.CircuitTerminationFilterSet
|
filterset = filtersets.CircuitTerminationFilterSet
|
||||||
filterset_form = forms.CircuitTerminationFilterForm
|
filterset_form = forms.CircuitTerminationFilterForm
|
||||||
table = tables.CircuitTerminationTable
|
table = tables.CircuitTerminationTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CircuitTermination)
|
@register_model_view(CircuitTermination)
|
||||||
@@ -526,6 +554,11 @@ class CircuitGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CircuitGroupBulkEditForm
|
form = forms.CircuitGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CircuitGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CircuitGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(CircuitGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class CircuitGroupBulkDeleteView(generic.BulkDeleteView):
|
class CircuitGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CircuitGroup.objects.all()
|
queryset = CircuitGroup.objects.all()
|
||||||
@@ -543,6 +576,7 @@ class CircuitGroupAssignmentListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
||||||
filterset_form = forms.CircuitGroupAssignmentFilterForm
|
filterset_form = forms.CircuitGroupAssignmentFilterForm
|
||||||
table = tables.CircuitGroupAssignmentTable
|
table = tables.CircuitGroupAssignmentTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CircuitGroupAssignment)
|
@register_model_view(CircuitGroupAssignment)
|
||||||
@@ -635,6 +669,11 @@ class VirtualCircuitTypeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VirtualCircuitTypeBulkEditForm
|
form = forms.VirtualCircuitTypeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuitType, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VirtualCircuitTypeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VirtualCircuitType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualCircuitType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VirtualCircuitType, 'bulk_delete', path='delete', detail=False)
|
||||||
class VirtualCircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
class VirtualCircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VirtualCircuitType.objects.annotate(
|
queryset = VirtualCircuitType.objects.annotate(
|
||||||
@@ -697,6 +736,12 @@ class VirtualCircuitBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VirtualCircuitBulkEditForm
|
form = forms.VirtualCircuitBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualCircuit, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VirtualCircuitulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VirtualCircuit.objects.all()
|
||||||
|
field_name = 'cid'
|
||||||
|
|
||||||
|
|
||||||
class VirtualCircuitBulkDeleteView(generic.BulkDeleteView):
|
class VirtualCircuitBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VirtualCircuit.objects.annotate(
|
queryset = VirtualCircuit.objects.annotate(
|
||||||
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||||
@@ -714,6 +759,7 @@ class VirtualCircuitTerminationListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||||
filterset_form = forms.VirtualCircuitTerminationFilterForm
|
filterset_form = forms.VirtualCircuitTerminationFilterForm
|
||||||
table = tables.VirtualCircuitTerminationTable
|
table = tables.VirtualCircuitTerminationTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualCircuitTermination)
|
@register_model_view(VirtualCircuitTermination)
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ class JobSerializer(BaseModelSerializer):
|
|||||||
model = Job
|
model = Job
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'object_type', 'object_id', 'name', 'status', 'created', 'scheduled',
|
'id', 'url', 'display_url', 'display', 'object_type', 'object_id', 'name', 'status', 'created', 'scheduled',
|
||||||
'interval', 'started', 'completed', 'user', 'data', 'error', 'job_id',
|
'interval', 'started', 'completed', 'user', 'data', 'error', 'job_id', 'log_entries',
|
||||||
]
|
]
|
||||||
brief_fields = ('url', 'created', 'completed', 'user', 'status')
|
brief_fields = ('url', 'created', 'completed', 'user', 'status')
|
||||||
|
|||||||
@@ -4,23 +4,31 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from rq.job import JobStatus
|
from rq.job import JobStatus
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'JOB_LOG_ENTRY_LEVELS',
|
||||||
'RQ_TASK_STATUSES',
|
'RQ_TASK_STATUSES',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Status:
|
class Badge:
|
||||||
label: str
|
label: str
|
||||||
color: str
|
color: str
|
||||||
|
|
||||||
|
|
||||||
RQ_TASK_STATUSES = {
|
RQ_TASK_STATUSES = {
|
||||||
JobStatus.QUEUED: Status(_('Queued'), 'cyan'),
|
JobStatus.QUEUED: Badge(_('Queued'), 'cyan'),
|
||||||
JobStatus.FINISHED: Status(_('Finished'), 'green'),
|
JobStatus.FINISHED: Badge(_('Finished'), 'green'),
|
||||||
JobStatus.FAILED: Status(_('Failed'), 'red'),
|
JobStatus.FAILED: Badge(_('Failed'), 'red'),
|
||||||
JobStatus.STARTED: Status(_('Started'), 'blue'),
|
JobStatus.STARTED: Badge(_('Started'), 'blue'),
|
||||||
JobStatus.DEFERRED: Status(_('Deferred'), 'gray'),
|
JobStatus.DEFERRED: Badge(_('Deferred'), 'gray'),
|
||||||
JobStatus.SCHEDULED: Status(_('Scheduled'), 'purple'),
|
JobStatus.SCHEDULED: Badge(_('Scheduled'), 'purple'),
|
||||||
JobStatus.STOPPED: Status(_('Stopped'), 'orange'),
|
JobStatus.STOPPED: Badge(_('Stopped'), 'orange'),
|
||||||
JobStatus.CANCELED: Status(_('Cancelled'), 'yellow'),
|
JobStatus.CANCELED: Badge(_('Cancelled'), 'yellow'),
|
||||||
|
}
|
||||||
|
|
||||||
|
JOB_LOG_ENTRY_LEVELS = {
|
||||||
|
'debug': Badge(_('Debug'), 'gray'),
|
||||||
|
'info': Badge(_('Info'), 'blue'),
|
||||||
|
'warning': Badge(_('Warning'), 'orange'),
|
||||||
|
'error': Badge(_('Error'), 'red'),
|
||||||
}
|
}
|
||||||
|
|||||||
21
netbox/core/dataclasses.py
Normal file
21
netbox/core/dataclasses.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'JobLogEntry',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class JobLogEntry:
|
||||||
|
level: str
|
||||||
|
message: str
|
||||||
|
timestamp: datetime = field(default_factory=timezone.now)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_logrecord(cls, record: logging.LogRecord):
|
||||||
|
return cls(record.levelname.lower(), record.msg)
|
||||||
@@ -7,7 +7,6 @@ from netbox.jobs import JobRunner, system_job
|
|||||||
from netbox.search.backends import search_backend
|
from netbox.search.backends import search_backend
|
||||||
from utilities.proxy import resolve_proxies
|
from utilities.proxy import resolve_proxies
|
||||||
from .choices import DataSourceStatusChoices, JobIntervalChoices
|
from .choices import DataSourceStatusChoices, JobIntervalChoices
|
||||||
from .exceptions import SyncError
|
|
||||||
from .models import DataSource
|
from .models import DataSource
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -23,19 +22,23 @@ class SyncDataSourceJob(JobRunner):
|
|||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
datasource = DataSource.objects.get(pk=self.job.object_id)
|
datasource = DataSource.objects.get(pk=self.job.object_id)
|
||||||
|
self.logger.debug(f"Found DataSource ID {datasource.pk}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self.logger.info(f"Syncing data source {datasource}")
|
||||||
datasource.sync()
|
datasource.sync()
|
||||||
|
|
||||||
# Update the search cache for DataFiles belonging to this source
|
# Update the search cache for DataFiles belonging to this source
|
||||||
|
self.logger.debug("Updating search cache for data files")
|
||||||
search_backend.cache(datasource.datafiles.iterator())
|
search_backend.cache(datasource.datafiles.iterator())
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error syncing data source: {e}")
|
||||||
DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED)
|
DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED)
|
||||||
if type(e) is SyncError:
|
|
||||||
logging.error(e)
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
self.logger.info("Syncing completed successfully")
|
||||||
|
|
||||||
|
|
||||||
@system_job(interval=JobIntervalChoices.INTERVAL_DAILY)
|
@system_job(interval=JobIntervalChoices.INTERVAL_DAILY)
|
||||||
class SystemHousekeepingJob(JobRunner):
|
class SystemHousekeepingJob(JobRunner):
|
||||||
|
|||||||
28
netbox/core/migrations/0016_job_log_entries.py
Normal file
28
netbox/core/migrations/0016_job_log_entries.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import utilities.json
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0015_remove_redundant_indexes'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='job',
|
||||||
|
name='log_entries',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.JSONField(
|
||||||
|
decoder=utilities.json.JobLogDecoder,
|
||||||
|
encoder=django.core.serializers.json.DjangoJSONEncoder
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
size=None
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
from dataclasses import asdict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import django_rq
|
import django_rq
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
@@ -14,8 +17,10 @@ from django.utils.translation import gettext as _
|
|||||||
from rq.exceptions import InvalidJobOperation
|
from rq.exceptions import InvalidJobOperation
|
||||||
|
|
||||||
from core.choices import JobStatusChoices
|
from core.choices import JobStatusChoices
|
||||||
|
from core.dataclasses import JobLogEntry
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from core.signals import job_end, job_start
|
from core.signals import job_end, job_start
|
||||||
|
from utilities.json import JobLogDecoder
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.rqworker import get_queue_for_model
|
from utilities.rqworker import get_queue_for_model
|
||||||
|
|
||||||
@@ -104,6 +109,15 @@ class Job(models.Model):
|
|||||||
verbose_name=_('job ID'),
|
verbose_name=_('job ID'),
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
|
log_entries = ArrayField(
|
||||||
|
verbose_name=_('log entries'),
|
||||||
|
base_field=models.JSONField(
|
||||||
|
encoder=DjangoJSONEncoder,
|
||||||
|
decoder=JobLogDecoder,
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
@@ -205,6 +219,13 @@ class Job(models.Model):
|
|||||||
# Send signal
|
# Send signal
|
||||||
job_end.send(self)
|
job_end.send(self)
|
||||||
|
|
||||||
|
def log(self, record: logging.LogRecord):
|
||||||
|
"""
|
||||||
|
Record a LogRecord from Python's native logging in the job's log.
|
||||||
|
"""
|
||||||
|
entry = JobLogEntry.from_logrecord(record)
|
||||||
|
self.log_entries.append(asdict(entry))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enqueue(
|
def enqueue(
|
||||||
cls,
|
cls,
|
||||||
|
|||||||
18
netbox/core/object_actions.py
Normal file
18
netbox/core/object_actions.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from netbox.object_actions import ObjectAction
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BulkSync',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BulkSync(ObjectAction):
|
||||||
|
"""
|
||||||
|
Synchronize multiple objects at once.
|
||||||
|
"""
|
||||||
|
name = 'bulk_sync'
|
||||||
|
label = _('Sync Data')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'sync'}
|
||||||
|
template_name = 'core/buttons/bulk_sync.html'
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from core.constants import RQ_TASK_STATUSES
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BackendTypeColumn',
|
'BackendTypeColumn',
|
||||||
'RQJobStatusColumn',
|
'BadgeColumn',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -23,14 +22,21 @@ class BackendTypeColumn(tables.Column):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class RQJobStatusColumn(tables.Column):
|
class BadgeColumn(tables.Column):
|
||||||
"""
|
"""
|
||||||
Render a colored label for the status of an RQ job.
|
Render a colored badge for a value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
badges: A dictionary mapping of values to core.constants.Badge instances.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, badges, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.badges = badges
|
||||||
|
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
status = RQ_TASK_STATUSES.get(value)
|
badge = self.badges.get(value)
|
||||||
return mark_safe(f'<span class="badge text-bg-{status.color}">{status.label}</span>')
|
return mark_safe(f'<span class="badge text-bg-{badge.color}">{badge.label}</span>')
|
||||||
|
|
||||||
def value(self, value):
|
def value(self, value):
|
||||||
status = RQ_TASK_STATUSES.get(value)
|
badge = self.badges.get(value)
|
||||||
return status.label
|
return badge.label
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import BaseTable, NetBoxTable, columns
|
||||||
from ..models import Job
|
from core.constants import JOB_LOG_ENTRY_LEVELS
|
||||||
|
from core.models import Job
|
||||||
|
from core.tables.columns import BadgeColumn
|
||||||
|
|
||||||
|
|
||||||
class JobTable(NetBoxTable):
|
class JobTable(NetBoxTable):
|
||||||
@@ -40,6 +42,9 @@ class JobTable(NetBoxTable):
|
|||||||
completed = columns.DateTimeColumn(
|
completed = columns.DateTimeColumn(
|
||||||
verbose_name=_('Completed'),
|
verbose_name=_('Completed'),
|
||||||
)
|
)
|
||||||
|
log_entries = tables.Column(
|
||||||
|
verbose_name=_('Log Entries'),
|
||||||
|
)
|
||||||
actions = columns.ActionsColumn(
|
actions = columns.ActionsColumn(
|
||||||
actions=('delete',)
|
actions=('delete',)
|
||||||
)
|
)
|
||||||
@@ -53,3 +58,24 @@ class JobTable(NetBoxTable):
|
|||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'id', 'object_type', 'object', 'name', 'status', 'created', 'started', 'completed', 'user',
|
'pk', 'id', 'object_type', 'object', 'name', 'status', 'created', 'started', 'completed', 'user',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def render_log_entries(self, value):
|
||||||
|
return len(value)
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogEntryTable(BaseTable):
|
||||||
|
timestamp = columns.DateTimeColumn(
|
||||||
|
timespec='milliseconds',
|
||||||
|
verbose_name=_('Time'),
|
||||||
|
)
|
||||||
|
level = BadgeColumn(
|
||||||
|
badges=JOB_LOG_ENTRY_LEVELS,
|
||||||
|
verbose_name=_('Level'),
|
||||||
|
)
|
||||||
|
message = tables.Column(
|
||||||
|
verbose_name=_('Message'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
empty_text = _('No log entries')
|
||||||
|
fields = ('timestamp', 'level', 'message')
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import django_tables2 as tables
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_tables2.utils import A
|
from django_tables2.utils import A
|
||||||
|
|
||||||
from core.tables.columns import RQJobStatusColumn
|
from core.constants import RQ_TASK_STATUSES
|
||||||
|
from core.tables.columns import BadgeColumn
|
||||||
from netbox.tables import BaseTable, columns
|
from netbox.tables import BaseTable, columns
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +85,8 @@ class BackgroundTaskTable(BaseTable):
|
|||||||
ended_at = columns.DateTimeColumn(
|
ended_at = columns.DateTimeColumn(
|
||||||
verbose_name=_("Ended")
|
verbose_name=_("Ended")
|
||||||
)
|
)
|
||||||
status = RQJobStatusColumn(
|
status = BadgeColumn(
|
||||||
|
badges=RQ_TASK_STATUSES,
|
||||||
verbose_name=_("Status"),
|
verbose_name=_("Status"),
|
||||||
accessor='get_status'
|
accessor='get_status'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from rq.worker_registration import clean_worker_registry
|
|||||||
|
|
||||||
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, requeue_rq_job, stop_rq_job
|
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs_from_status, requeue_rq_job, stop_rq_job
|
||||||
from netbox.config import get_config, PARAMS
|
from netbox.config import get_config, PARAMS
|
||||||
|
from netbox.object_actions import AddObject, BulkDelete, BulkExport, DeleteObject
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox.views.generic.base import BaseObjectView
|
from netbox.views.generic.base import BaseObjectView
|
||||||
@@ -31,13 +32,13 @@ from utilities.forms import ConfirmationForm
|
|||||||
from utilities.htmx import htmx_partial
|
from utilities.htmx import htmx_partial
|
||||||
from utilities.json import ConfigJSONEncoder
|
from utilities.json import ConfigJSONEncoder
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.views import ContentTypePermissionRequiredMixin, GetRelatedModelsMixin, register_model_view
|
from utilities.views import ContentTypePermissionRequiredMixin, GetRelatedModelsMixin, ViewTab, register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import DataSourceStatusChoices
|
from .choices import DataSourceStatusChoices
|
||||||
from .jobs import SyncDataSourceJob
|
from .jobs import SyncDataSourceJob
|
||||||
from .models import *
|
from .models import *
|
||||||
from .plugins import get_catalog_plugins, get_local_plugins
|
from .plugins import get_catalog_plugins, get_local_plugins
|
||||||
from .tables import CatalogPluginTable, PluginVersionTable
|
from .tables import CatalogPluginTable, JobLogEntryTable, PluginVersionTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -119,6 +120,11 @@ class DataSourceBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.DataSourceBulkEditForm
|
form = forms.DataSourceBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DataSource, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class DataSourceBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = DataSource.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(DataSource, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(DataSource, 'bulk_delete', path='delete', detail=False)
|
||||||
class DataSourceBulkDeleteView(generic.BulkDeleteView):
|
class DataSourceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = DataSource.objects.annotate(
|
queryset = DataSource.objects.annotate(
|
||||||
@@ -138,14 +144,13 @@ class DataFileListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.DataFileFilterSet
|
filterset = filtersets.DataFileFilterSet
|
||||||
filterset_form = forms.DataFileFilterForm
|
filterset_form = forms.DataFileFilterForm
|
||||||
table = tables.DataFileTable
|
table = tables.DataFileTable
|
||||||
actions = {
|
actions = (BulkDelete,)
|
||||||
'bulk_delete': {'delete'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(DataFile)
|
@register_model_view(DataFile)
|
||||||
class DataFileView(generic.ObjectView):
|
class DataFileView(generic.ObjectView):
|
||||||
queryset = DataFile.objects.all()
|
queryset = DataFile.objects.all()
|
||||||
|
actions = (DeleteObject,)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(DataFile, 'delete')
|
@register_model_view(DataFile, 'delete')
|
||||||
@@ -170,15 +175,32 @@ class JobListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.JobFilterSet
|
filterset = filtersets.JobFilterSet
|
||||||
filterset_form = forms.JobFilterForm
|
filterset_form = forms.JobFilterForm
|
||||||
table = tables.JobTable
|
table = tables.JobTable
|
||||||
actions = {
|
actions = (BulkExport, BulkDelete)
|
||||||
'export': {'view'},
|
|
||||||
'bulk_delete': {'delete'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Job)
|
@register_model_view(Job)
|
||||||
class JobView(generic.ObjectView):
|
class JobView(generic.ObjectView):
|
||||||
queryset = Job.objects.all()
|
queryset = Job.objects.all()
|
||||||
|
actions = (DeleteObject,)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Job, 'log')
|
||||||
|
class JobLogView(generic.ObjectView):
|
||||||
|
queryset = Job.objects.all()
|
||||||
|
actions = (DeleteObject,)
|
||||||
|
template_name = 'core/job/log.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Log'),
|
||||||
|
badge=lambda obj: len(obj.log_entries),
|
||||||
|
weight=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
table = JobLogEntryTable(instance.log_entries)
|
||||||
|
table.configure(request)
|
||||||
|
return {
|
||||||
|
'table': table,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Job, 'delete')
|
@register_model_view(Job, 'delete')
|
||||||
@@ -204,9 +226,7 @@ class ObjectChangeListView(generic.ObjectListView):
|
|||||||
filterset_form = forms.ObjectChangeFilterForm
|
filterset_form = forms.ObjectChangeFilterForm
|
||||||
table = tables.ObjectChangeTable
|
table = tables.ObjectChangeTable
|
||||||
template_name = 'core/objectchange_list.html'
|
template_name = 'core/objectchange_list.html'
|
||||||
actions = {
|
actions = (BulkExport,)
|
||||||
'export': {'view'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ObjectChange)
|
@register_model_view(ObjectChange)
|
||||||
@@ -274,6 +294,7 @@ class ConfigRevisionListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ConfigRevisionFilterSet
|
filterset = filtersets.ConfigRevisionFilterSet
|
||||||
filterset_form = forms.ConfigRevisionFilterForm
|
filterset_form = forms.ConfigRevisionFilterForm
|
||||||
table = tables.ConfigRevisionTable
|
table = tables.ConfigRevisionTable
|
||||||
|
actions = (AddObject, BulkExport)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConfigRevision)
|
@register_model_view(ConfigRevision)
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0207_remove_redundant_indexes'),
|
||||||
|
('extras', '0129_fix_script_paths'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='platform',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='platform',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='platform',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=('manufacturer', 'name'),
|
||||||
|
name='dcim_platform_manufacturer_name'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='platform',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
condition=models.Q(('manufacturer__isnull', True)),
|
||||||
|
fields=('name',),
|
||||||
|
name='dcim_platform_name',
|
||||||
|
violation_error_message='Platform name must be unique.'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='platform',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=('manufacturer', 'slug'),
|
||||||
|
name='dcim_platform_manufacturer_slug'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='platform',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
condition=models.Q(('manufacturer__isnull', True)),
|
||||||
|
fields=('slug',),
|
||||||
|
name='dcim_platform_slug',
|
||||||
|
violation_error_message='Platform slug must be unique.'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -415,6 +415,15 @@ class Platform(OrganizationalModel):
|
|||||||
null=True,
|
null=True,
|
||||||
help_text=_('Optionally limit this platform to devices of a certain manufacturer')
|
help_text=_('Optionally limit this platform to devices of a certain manufacturer')
|
||||||
)
|
)
|
||||||
|
# Override name & slug from OrganizationalModel to not enforce uniqueness
|
||||||
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
slug = models.SlugField(
|
||||||
|
verbose_name=_('slug'),
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
config_template = models.ForeignKey(
|
config_template = models.ForeignKey(
|
||||||
to='extras.ConfigTemplate',
|
to='extras.ConfigTemplate',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@@ -427,6 +436,28 @@ class Platform(OrganizationalModel):
|
|||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
verbose_name = _('platform')
|
verbose_name = _('platform')
|
||||||
verbose_name_plural = _('platforms')
|
verbose_name_plural = _('platforms')
|
||||||
|
constraints = (
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('manufacturer', 'name'),
|
||||||
|
name='%(app_label)s_%(class)s_manufacturer_name',
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('name',),
|
||||||
|
name='%(app_label)s_%(class)s_name',
|
||||||
|
condition=Q(manufacturer__isnull=True),
|
||||||
|
violation_error_message=_("Platform name must be unique.")
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('manufacturer', 'slug'),
|
||||||
|
name='%(app_label)s_%(class)s_manufacturer_slug',
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('slug',),
|
||||||
|
name='%(app_label)s_%(class)s_slug',
|
||||||
|
condition=Q(manufacturer__isnull=True),
|
||||||
|
violation_error_message=_("Platform slug must be unique.")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Device(
|
class Device(
|
||||||
|
|||||||
38
netbox/dcim/object_actions.py
Normal file
38
netbox/dcim/object_actions.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from netbox.object_actions import ObjectAction
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BulkAddComponents',
|
||||||
|
'BulkDisconnect',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BulkAddComponents(ObjectAction):
|
||||||
|
"""
|
||||||
|
Add components to the selected devices.
|
||||||
|
"""
|
||||||
|
label = _('Add Components')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'change'}
|
||||||
|
template_name = 'dcim/buttons/bulk_add_components.html'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_context(cls, context, obj):
|
||||||
|
return {
|
||||||
|
'perms': context.get('perms'),
|
||||||
|
'request': context.get('request'),
|
||||||
|
'formaction': context.get('formaction'),
|
||||||
|
'label': cls.label,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BulkDisconnect(ObjectAction):
|
||||||
|
"""
|
||||||
|
Disconnect each of a set of objects to which a cable is connected.
|
||||||
|
"""
|
||||||
|
name = 'bulk_disconnect'
|
||||||
|
label = _('Disconnect Selected')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'change'}
|
||||||
|
template_name = 'dcim/buttons/bulk_disconnect.html'
|
||||||
@@ -15,7 +15,7 @@ from circuits.models import Circuit, CircuitTermination
|
|||||||
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||||
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
|
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
|
||||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.object_actions import *
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
@@ -34,6 +34,7 @@ from wireless.models import WirelessLAN
|
|||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import DeviceFaceChoices, InterfaceModeChoices
|
from .choices import DeviceFaceChoices, InterfaceModeChoices
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .object_actions import BulkAddComponents, BulkDisconnect
|
||||||
|
|
||||||
CABLE_TERMINATION_TYPES = {
|
CABLE_TERMINATION_TYPES = {
|
||||||
'dcim.consoleport': ConsolePort,
|
'dcim.consoleport': ConsolePort,
|
||||||
@@ -49,11 +50,6 @@ CABLE_TERMINATION_TYPES = {
|
|||||||
|
|
||||||
|
|
||||||
class DeviceComponentsView(generic.ObjectChildrenView):
|
class DeviceComponentsView(generic.ObjectChildrenView):
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
'bulk_disconnect': {'change'},
|
|
||||||
}
|
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@@ -61,12 +57,8 @@ class DeviceComponentsView(generic.ObjectChildrenView):
|
|||||||
|
|
||||||
|
|
||||||
class DeviceTypeComponentsView(generic.ObjectChildrenView):
|
class DeviceTypeComponentsView(generic.ObjectChildrenView):
|
||||||
actions = {
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
queryset = DeviceType.objects.all()
|
queryset = DeviceType.objects.all()
|
||||||
template_name = 'dcim/devicetype/component_templates.html'
|
|
||||||
viewname = None # Used for return_url resolution
|
viewname = None # Used for return_url resolution
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@@ -78,9 +70,9 @@ class DeviceTypeComponentsView(generic.ObjectChildrenView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeComponentsView(DeviceComponentsView):
|
class ModuleTypeComponentsView(generic.ObjectChildrenView):
|
||||||
queryset = ModuleType.objects.all()
|
queryset = ModuleType.objects.all()
|
||||||
template_name = 'dcim/moduletype/component_templates.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
viewname = None # Used for return_url resolution
|
viewname = None # Used for return_url resolution
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@@ -300,6 +292,11 @@ class RegionBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RegionBulkEditForm
|
form = forms.RegionBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Region, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RegionBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Region.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Region, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Region, 'bulk_delete', path='delete', detail=False)
|
||||||
class RegionBulkDeleteView(generic.BulkDeleteView):
|
class RegionBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Region.objects.add_related_count(
|
queryset = Region.objects.add_related_count(
|
||||||
@@ -426,6 +423,11 @@ class SiteGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.SiteGroupBulkEditForm
|
form = forms.SiteGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SiteGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class SiteGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = SiteGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(SiteGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(SiteGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class SiteGroupBulkDeleteView(generic.BulkDeleteView):
|
class SiteGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SiteGroup.objects.add_related_count(
|
queryset = SiteGroup.objects.add_related_count(
|
||||||
@@ -511,6 +513,11 @@ class SiteBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.SiteBulkEditForm
|
form = forms.SiteBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Site, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class SiteBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Site.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Site, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Site, 'bulk_delete', path='delete', detail=False)
|
||||||
class SiteBulkDeleteView(generic.BulkDeleteView):
|
class SiteBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Site.objects.all()
|
queryset = Site.objects.all()
|
||||||
@@ -615,6 +622,11 @@ class LocationBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.LocationBulkEditForm
|
form = forms.LocationBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Location, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class LocationBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Location.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Location, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Location, 'bulk_delete', path='delete', detail=False)
|
||||||
class LocationBulkDeleteView(generic.BulkDeleteView):
|
class LocationBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Location.objects.add_related_count(
|
queryset = Location.objects.add_related_count(
|
||||||
@@ -680,6 +692,11 @@ class RackRoleBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RackRoleBulkEditForm
|
form = forms.RackRoleBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(RackRole, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RackRoleBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = RackRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RackRole, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(RackRole, 'bulk_delete', path='delete', detail=False)
|
||||||
class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RackRole.objects.annotate(
|
queryset = RackRole.objects.annotate(
|
||||||
@@ -739,6 +756,12 @@ class RackTypeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RackTypeBulkEditForm
|
form = forms.RackTypeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(RackType, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RackTypeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = RackType.objects.all()
|
||||||
|
field_name = 'model'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RackType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(RackType, 'bulk_delete', path='delete', detail=False)
|
||||||
class RackTypeBulkDeleteView(generic.BulkDeleteView):
|
class RackTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RackType.objects.all()
|
queryset = RackType.objects.all()
|
||||||
@@ -918,6 +941,11 @@ class RackBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RackBulkEditForm
|
form = forms.RackBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Rack, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RackBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Rack.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Rack, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Rack, 'bulk_delete', path='delete', detail=False)
|
||||||
class RackBulkDeleteView(generic.BulkDeleteView):
|
class RackBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Rack.objects.all()
|
queryset = Rack.objects.all()
|
||||||
@@ -935,6 +963,7 @@ class RackReservationListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.RackReservationFilterSet
|
filterset = filtersets.RackReservationFilterSet
|
||||||
filterset_form = forms.RackReservationFilterForm
|
filterset_form = forms.RackReservationFilterForm
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RackReservation)
|
@register_model_view(RackReservation)
|
||||||
@@ -1051,6 +1080,11 @@ class ManufacturerBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ManufacturerBulkEditForm
|
form = forms.ManufacturerBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Manufacturer, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ManufacturerBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Manufacturer.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Manufacturer, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Manufacturer, 'bulk_delete', path='delete', detail=False)
|
||||||
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Manufacturer.objects.annotate(
|
queryset = Manufacturer.objects.annotate(
|
||||||
@@ -1298,6 +1332,12 @@ class DeviceTypeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.DeviceTypeBulkEditForm
|
form = forms.DeviceTypeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class DeviceTypeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = DeviceType.objects.all()
|
||||||
|
field_name = 'model'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(DeviceType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(DeviceType, 'bulk_delete', path='delete', detail=False)
|
||||||
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = DeviceType.objects.annotate(
|
queryset = DeviceType.objects.annotate(
|
||||||
@@ -1354,6 +1394,11 @@ class ModuleTypeProfileBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ModuleTypeProfileBulkEditForm
|
form = forms.ModuleTypeProfileBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ModuleTypeProfileBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ModuleTypeProfile.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ModuleTypeProfile, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ModuleTypeProfile, 'bulk_delete', path='delete', detail=False)
|
||||||
class ModuleTypeProfileBulkDeleteView(generic.BulkDeleteView):
|
class ModuleTypeProfileBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ModuleTypeProfile.objects.annotate(
|
queryset = ModuleTypeProfile.objects.annotate(
|
||||||
@@ -1564,6 +1609,11 @@ class ModuleTypeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ModuleTypeBulkEditForm
|
form = forms.ModuleTypeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ModuleTypeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ModuleType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)
|
||||||
class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
|
class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ModuleType.objects.annotate(
|
queryset = ModuleType.objects.annotate(
|
||||||
@@ -2038,6 +2088,11 @@ class DeviceRoleBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.DeviceRoleBulkEditForm
|
form = forms.DeviceRoleBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceRole, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class DeviceRoleBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = DeviceRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(DeviceRole, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(DeviceRole, 'bulk_delete', path='delete', detail=False)
|
||||||
class DeviceRoleBulkDeleteView(generic.BulkDeleteView):
|
class DeviceRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = DeviceRole.objects.annotate(
|
queryset = DeviceRole.objects.annotate(
|
||||||
@@ -2099,6 +2154,11 @@ class PlatformBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.PlatformBulkEditForm
|
form = forms.PlatformBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Platform, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class PlatformBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Platform.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Platform, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Platform, 'bulk_delete', path='delete', detail=False)
|
||||||
class PlatformBulkDeleteView(generic.BulkDeleteView):
|
class PlatformBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
@@ -2116,7 +2176,7 @@ class DeviceListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
filterset_form = forms.DeviceFilterForm
|
filterset_form = forms.DeviceFilterForm
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
template_name = 'dcim/device_list.html'
|
actions = (AddObject, BulkImport, BulkExport, BulkAddComponents, BulkEdit, BulkRename, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Device)
|
@register_model_view(Device)
|
||||||
@@ -2157,7 +2217,7 @@ class DeviceConsolePortsView(DeviceComponentsView):
|
|||||||
table = tables.DeviceConsolePortTable
|
table = tables.DeviceConsolePortTable
|
||||||
filterset = filtersets.ConsolePortFilterSet
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
filterset_form = forms.ConsolePortFilterForm
|
filterset_form = forms.ConsolePortFilterForm
|
||||||
template_name = 'dcim/device/consoleports.html',
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Console Ports'),
|
label=_('Console Ports'),
|
||||||
badge=lambda obj: obj.console_port_count,
|
badge=lambda obj: obj.console_port_count,
|
||||||
@@ -2173,7 +2233,7 @@ class DeviceConsoleServerPortsView(DeviceComponentsView):
|
|||||||
table = tables.DeviceConsoleServerPortTable
|
table = tables.DeviceConsoleServerPortTable
|
||||||
filterset = filtersets.ConsoleServerPortFilterSet
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
filterset_form = forms.ConsoleServerPortFilterForm
|
filterset_form = forms.ConsoleServerPortFilterForm
|
||||||
template_name = 'dcim/device/consoleserverports.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Console Server Ports'),
|
label=_('Console Server Ports'),
|
||||||
badge=lambda obj: obj.console_server_port_count,
|
badge=lambda obj: obj.console_server_port_count,
|
||||||
@@ -2189,7 +2249,7 @@ class DevicePowerPortsView(DeviceComponentsView):
|
|||||||
table = tables.DevicePowerPortTable
|
table = tables.DevicePowerPortTable
|
||||||
filterset = filtersets.PowerPortFilterSet
|
filterset = filtersets.PowerPortFilterSet
|
||||||
filterset_form = forms.PowerPortFilterForm
|
filterset_form = forms.PowerPortFilterForm
|
||||||
template_name = 'dcim/device/powerports.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Power Ports'),
|
label=_('Power Ports'),
|
||||||
badge=lambda obj: obj.power_port_count,
|
badge=lambda obj: obj.power_port_count,
|
||||||
@@ -2205,7 +2265,7 @@ class DevicePowerOutletsView(DeviceComponentsView):
|
|||||||
table = tables.DevicePowerOutletTable
|
table = tables.DevicePowerOutletTable
|
||||||
filterset = filtersets.PowerOutletFilterSet
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
filterset_form = forms.PowerOutletFilterForm
|
filterset_form = forms.PowerOutletFilterForm
|
||||||
template_name = 'dcim/device/poweroutlets.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Power Outlets'),
|
label=_('Power Outlets'),
|
||||||
badge=lambda obj: obj.power_outlet_count,
|
badge=lambda obj: obj.power_outlet_count,
|
||||||
@@ -2221,6 +2281,7 @@ class DeviceInterfacesView(DeviceComponentsView):
|
|||||||
table = tables.DeviceInterfaceTable
|
table = tables.DeviceInterfaceTable
|
||||||
filterset = filtersets.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
filterset_form = forms.InterfaceFilterForm
|
filterset_form = forms.InterfaceFilterForm
|
||||||
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
template_name = 'dcim/device/interfaces.html'
|
template_name = 'dcim/device/interfaces.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Interfaces'),
|
label=_('Interfaces'),
|
||||||
@@ -2243,7 +2304,7 @@ class DeviceFrontPortsView(DeviceComponentsView):
|
|||||||
table = tables.DeviceFrontPortTable
|
table = tables.DeviceFrontPortTable
|
||||||
filterset = filtersets.FrontPortFilterSet
|
filterset = filtersets.FrontPortFilterSet
|
||||||
filterset_form = forms.FrontPortFilterForm
|
filterset_form = forms.FrontPortFilterForm
|
||||||
template_name = 'dcim/device/frontports.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Front Ports'),
|
label=_('Front Ports'),
|
||||||
badge=lambda obj: obj.front_port_count,
|
badge=lambda obj: obj.front_port_count,
|
||||||
@@ -2259,7 +2320,7 @@ class DeviceRearPortsView(DeviceComponentsView):
|
|||||||
table = tables.DeviceRearPortTable
|
table = tables.DeviceRearPortTable
|
||||||
filterset = filtersets.RearPortFilterSet
|
filterset = filtersets.RearPortFilterSet
|
||||||
filterset_form = forms.RearPortFilterForm
|
filterset_form = forms.RearPortFilterForm
|
||||||
template_name = 'dcim/device/rearports.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDisconnect, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Rear Ports'),
|
label=_('Rear Ports'),
|
||||||
badge=lambda obj: obj.rear_port_count,
|
badge=lambda obj: obj.rear_port_count,
|
||||||
@@ -2275,11 +2336,7 @@ class DeviceModuleBaysView(DeviceComponentsView):
|
|||||||
table = tables.DeviceModuleBayTable
|
table = tables.DeviceModuleBayTable
|
||||||
filterset = filtersets.ModuleBayFilterSet
|
filterset = filtersets.ModuleBayFilterSet
|
||||||
filterset_form = forms.ModuleBayFilterForm
|
filterset_form = forms.ModuleBayFilterForm
|
||||||
template_name = 'dcim/device/modulebays.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Module Bays'),
|
label=_('Module Bays'),
|
||||||
badge=lambda obj: obj.module_bay_count,
|
badge=lambda obj: obj.module_bay_count,
|
||||||
@@ -2295,11 +2352,7 @@ class DeviceDeviceBaysView(DeviceComponentsView):
|
|||||||
table = tables.DeviceDeviceBayTable
|
table = tables.DeviceDeviceBayTable
|
||||||
filterset = filtersets.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
filterset_form = forms.DeviceBayFilterForm
|
filterset_form = forms.DeviceBayFilterForm
|
||||||
template_name = 'dcim/device/devicebays.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Device Bays'),
|
label=_('Device Bays'),
|
||||||
badge=lambda obj: obj.device_bay_count,
|
badge=lambda obj: obj.device_bay_count,
|
||||||
@@ -2315,11 +2368,7 @@ class DeviceInventoryView(DeviceComponentsView):
|
|||||||
table = tables.DeviceInventoryItemTable
|
table = tables.DeviceInventoryItemTable
|
||||||
filterset = filtersets.InventoryItemFilterSet
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
filterset_form = forms.InventoryItemFilterForm
|
filterset_form = forms.InventoryItemFilterForm
|
||||||
template_name = 'dcim/device/inventory.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Inventory Items'),
|
label=_('Inventory Items'),
|
||||||
badge=lambda obj: obj.inventory_item_count,
|
badge=lambda obj: obj.inventory_item_count,
|
||||||
@@ -2393,16 +2442,16 @@ class DeviceBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.DeviceBulkEditForm
|
form = forms.DeviceBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Device, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Device, 'bulk_rename', path='rename', detail=False)
|
||||||
class DeviceBulkDeleteView(generic.BulkDeleteView):
|
class DeviceBulkRenameView(generic.BulkRenameView):
|
||||||
queryset = Device.objects.prefetch_related('device_type__manufacturer')
|
queryset = Device.objects.all()
|
||||||
filterset = filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Device, 'bulk_rename', path='rename', detail=False)
|
@register_model_view(Device, 'bulk_delete', path='delete', detail=False)
|
||||||
class DeviceBulkRenameView(generic.BulkRenameView):
|
class DeviceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.prefetch_related('device_type__manufacturer')
|
||||||
filterset = filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
|
|
||||||
@@ -2417,6 +2466,7 @@ class ModuleListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ModuleFilterSet
|
filterset = filtersets.ModuleFilterSet
|
||||||
filterset_form = forms.ModuleFilterForm
|
filterset_form = forms.ModuleFilterForm
|
||||||
table = tables.ModuleTable
|
table = tables.ModuleTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Module)
|
@register_model_view(Module)
|
||||||
@@ -2472,11 +2522,6 @@ class ConsolePortListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ConsolePortFilterSet
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
filterset_form = forms.ConsolePortFilterForm
|
filterset_form = forms.ConsolePortFilterForm
|
||||||
table = tables.ConsolePortTable
|
table = tables.ConsolePortTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConsolePort)
|
@register_model_view(ConsolePort)
|
||||||
@@ -2547,11 +2592,6 @@ class ConsoleServerPortListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ConsoleServerPortFilterSet
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
filterset_form = forms.ConsoleServerPortFilterForm
|
filterset_form = forms.ConsoleServerPortFilterForm
|
||||||
table = tables.ConsoleServerPortTable
|
table = tables.ConsoleServerPortTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConsoleServerPort)
|
@register_model_view(ConsoleServerPort)
|
||||||
@@ -2622,11 +2662,6 @@ class PowerPortListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.PowerPortFilterSet
|
filterset = filtersets.PowerPortFilterSet
|
||||||
filterset_form = forms.PowerPortFilterForm
|
filterset_form = forms.PowerPortFilterForm
|
||||||
table = tables.PowerPortTable
|
table = tables.PowerPortTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(PowerPort)
|
@register_model_view(PowerPort)
|
||||||
@@ -2697,11 +2732,6 @@ class PowerOutletListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.PowerOutletFilterSet
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
filterset_form = forms.PowerOutletFilterForm
|
filterset_form = forms.PowerOutletFilterForm
|
||||||
table = tables.PowerOutletTable
|
table = tables.PowerOutletTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(PowerOutlet)
|
@register_model_view(PowerOutlet)
|
||||||
@@ -2772,11 +2802,6 @@ class InterfaceListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
filterset_form = forms.InterfaceFilterForm
|
filterset_form = forms.InterfaceFilterForm
|
||||||
table = tables.InterfaceTable
|
table = tables.InterfaceTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Interface)
|
@register_model_view(Interface)
|
||||||
@@ -2920,11 +2945,6 @@ class FrontPortListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.FrontPortFilterSet
|
filterset = filtersets.FrontPortFilterSet
|
||||||
filterset_form = forms.FrontPortFilterForm
|
filterset_form = forms.FrontPortFilterForm
|
||||||
table = tables.FrontPortTable
|
table = tables.FrontPortTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(FrontPort)
|
@register_model_view(FrontPort)
|
||||||
@@ -2995,11 +3015,6 @@ class RearPortListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.RearPortFilterSet
|
filterset = filtersets.RearPortFilterSet
|
||||||
filterset_form = forms.RearPortFilterForm
|
filterset_form = forms.RearPortFilterForm
|
||||||
table = tables.RearPortTable
|
table = tables.RearPortTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RearPort)
|
@register_model_view(RearPort)
|
||||||
@@ -3070,11 +3085,6 @@ class ModuleBayListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ModuleBayFilterSet
|
filterset = filtersets.ModuleBayFilterSet
|
||||||
filterset_form = forms.ModuleBayFilterForm
|
filterset_form = forms.ModuleBayFilterForm
|
||||||
table = tables.ModuleBayTable
|
table = tables.ModuleBayTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ModuleBay)
|
@register_model_view(ModuleBay)
|
||||||
@@ -3136,11 +3146,6 @@ class DeviceBayListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
filterset_form = forms.DeviceBayFilterForm
|
filterset_form = forms.DeviceBayFilterForm
|
||||||
table = tables.DeviceBayTable
|
table = tables.DeviceBayTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(DeviceBay)
|
@register_model_view(DeviceBay)
|
||||||
@@ -3283,11 +3288,6 @@ class InventoryItemListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.InventoryItemFilterSet
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
filterset_form = forms.InventoryItemFilterForm
|
filterset_form = forms.InventoryItemFilterForm
|
||||||
table = tables.InventoryItemTable
|
table = tables.InventoryItemTable
|
||||||
template_name = 'dcim/component_list.html'
|
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(InventoryItem)
|
@register_model_view(InventoryItem)
|
||||||
@@ -3410,6 +3410,11 @@ class InventoryItemRoleBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.InventoryItemRoleBulkEditForm
|
form = forms.InventoryItemRoleBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(InventoryItemRole, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class InventoryItemRoleBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = InventoryItemRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(InventoryItemRole, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(InventoryItemRole, 'bulk_delete', path='delete', detail=False)
|
||||||
class InventoryItemRoleBulkDeleteView(generic.BulkDeleteView):
|
class InventoryItemRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = InventoryItemRole.objects.annotate(
|
queryset = InventoryItemRole.objects.annotate(
|
||||||
@@ -3607,6 +3612,12 @@ class CableBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CableBulkEditForm
|
form = forms.CableBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Cable, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CableBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Cable.objects.all()
|
||||||
|
field_name = 'label'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Cable, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Cable, 'bulk_delete', path='delete', detail=False)
|
||||||
class CableBulkDeleteView(generic.BulkDeleteView):
|
class CableBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Cable.objects.prefetch_related(
|
queryset = Cable.objects.prefetch_related(
|
||||||
@@ -3627,9 +3638,7 @@ class ConsoleConnectionsListView(generic.ObjectListView):
|
|||||||
filterset_form = forms.ConsoleConnectionFilterForm
|
filterset_form = forms.ConsoleConnectionFilterForm
|
||||||
table = tables.ConsoleConnectionTable
|
table = tables.ConsoleConnectionTable
|
||||||
template_name = 'dcim/connections_list.html'
|
template_name = 'dcim/connections_list.html'
|
||||||
actions = {
|
actions = (BulkExport,)
|
||||||
'export': {'view'},
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_extra_context(self, request):
|
def get_extra_context(self, request):
|
||||||
return {
|
return {
|
||||||
@@ -3643,9 +3652,7 @@ class PowerConnectionsListView(generic.ObjectListView):
|
|||||||
filterset_form = forms.PowerConnectionFilterForm
|
filterset_form = forms.PowerConnectionFilterForm
|
||||||
table = tables.PowerConnectionTable
|
table = tables.PowerConnectionTable
|
||||||
template_name = 'dcim/connections_list.html'
|
template_name = 'dcim/connections_list.html'
|
||||||
actions = {
|
actions = (BulkExport,)
|
||||||
'export': {'view'},
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_extra_context(self, request):
|
def get_extra_context(self, request):
|
||||||
return {
|
return {
|
||||||
@@ -3659,9 +3666,7 @@ class InterfaceConnectionsListView(generic.ObjectListView):
|
|||||||
filterset_form = forms.InterfaceConnectionFilterForm
|
filterset_form = forms.InterfaceConnectionFilterForm
|
||||||
table = tables.InterfaceConnectionTable
|
table = tables.InterfaceConnectionTable
|
||||||
template_name = 'dcim/connections_list.html'
|
template_name = 'dcim/connections_list.html'
|
||||||
actions = {
|
actions = (BulkExport,)
|
||||||
'export': {'view'},
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_extra_context(self, request):
|
def get_extra_context(self, request):
|
||||||
return {
|
return {
|
||||||
@@ -3905,6 +3910,11 @@ class VirtualChassisBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VirtualChassisBulkEditForm
|
form = forms.VirtualChassisBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualChassis, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VirtualChassisBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VirtualChassis.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualChassis, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VirtualChassis, 'bulk_delete', path='delete', detail=False)
|
||||||
class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VirtualChassis.objects.all()
|
queryset = VirtualChassis.objects.all()
|
||||||
@@ -3962,6 +3972,11 @@ class PowerPanelBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.PowerPanelBulkEditForm
|
form = forms.PowerPanelBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(PowerPanel, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class PowerPanelBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = PowerPanel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(PowerPanel, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(PowerPanel, 'bulk_delete', path='delete', detail=False)
|
||||||
class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = PowerPanel.objects.annotate(
|
queryset = PowerPanel.objects.annotate(
|
||||||
@@ -4014,6 +4029,11 @@ class PowerFeedBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.PowerFeedBulkEditForm
|
form = forms.PowerFeedBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(PowerFeed, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class PowerFeedBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = PowerFeed.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(PowerFeed, 'bulk_disconnect', path='disconnect', detail=False)
|
@register_model_view(PowerFeed, 'bulk_disconnect', path='disconnect', detail=False)
|
||||||
class PowerFeedBulkDisconnectView(BulkDisconnectView):
|
class PowerFeedBulkDisconnectView(BulkDisconnectView):
|
||||||
queryset = PowerFeed.objects.all()
|
queryset = PowerFeed.objects.all()
|
||||||
@@ -4042,6 +4062,7 @@ class VirtualDeviceContextListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.VirtualDeviceContextFilterSet
|
filterset = filtersets.VirtualDeviceContextFilterSet
|
||||||
filterset_form = forms.VirtualDeviceContextFilterForm
|
filterset_form = forms.VirtualDeviceContextFilterForm
|
||||||
table = tables.VirtualDeviceContextTable
|
table = tables.VirtualDeviceContextTable
|
||||||
|
actions = (AddObject, BulkImport, BulkEdit, BulkRename, BulkExport, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualDeviceContext)
|
@register_model_view(VirtualDeviceContext)
|
||||||
@@ -4086,6 +4107,11 @@ class VirtualDeviceContextBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VirtualDeviceContextBulkEditForm
|
form = forms.VirtualDeviceContextBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualDeviceContext, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VirtualDeviceContextBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VirtualDeviceContext.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualDeviceContext, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VirtualDeviceContext, 'bulk_delete', path='delete', detail=False)
|
||||||
class VirtualDeviceContextBulkDeleteView(generic.BulkDeleteView):
|
class VirtualDeviceContextBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VirtualDeviceContext.objects.all()
|
queryset = VirtualDeviceContext.objects.all()
|
||||||
@@ -4103,6 +4129,7 @@ class MACAddressListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.MACAddressFilterSet
|
filterset = filtersets.MACAddressFilterSet
|
||||||
filterset_form = forms.MACAddressFilterForm
|
filterset_form = forms.MACAddressFilterForm
|
||||||
table = tables.MACAddressTable
|
table = tables.MACAddressTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(MACAddress)
|
@register_model_view(MACAddress)
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ class ScriptJob(JobRunner):
|
|||||||
request: The WSGI request associated with this execution (if any)
|
request: The WSGI request associated with this execution (if any)
|
||||||
commit: Passed through to Script.run()
|
commit: Passed through to Script.run()
|
||||||
"""
|
"""
|
||||||
script = ScriptModel.objects.get(pk=self.job.object_id).python_class()
|
script_model = ScriptModel.objects.get(pk=self.job.object_id)
|
||||||
|
self.logger.debug(f"Found ScriptModel ID {script_model.pk}")
|
||||||
|
script = script_model.python_class()
|
||||||
|
self.logger.debug(f"Loaded script {script.full_name}")
|
||||||
|
|
||||||
# Add files to form data
|
# Add files to form data
|
||||||
if request:
|
if request:
|
||||||
@@ -100,6 +103,7 @@ class ScriptJob(JobRunner):
|
|||||||
|
|
||||||
# Add the current request as a property of the script
|
# Add the current request as a property of the script
|
||||||
script.request = request
|
script.request = request
|
||||||
|
self.logger.debug(f"Request ID: {request.id}")
|
||||||
|
|
||||||
# Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
|
# Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
|
||||||
# change logging, event rules, etc.
|
# change logging, event rules, etc.
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ from jinja2.exceptions import TemplateError
|
|||||||
|
|
||||||
from core.choices import ManagedFileRootPathChoices
|
from core.choices import ManagedFileRootPathChoices
|
||||||
from core.models import Job
|
from core.models import Job
|
||||||
|
from core.object_actions import BulkSync
|
||||||
from dcim.models import Device, DeviceRole, Platform
|
from dcim.models import Device, DeviceRole, Platform
|
||||||
from extras.choices import LogLevelChoices
|
from extras.choices import LogLevelChoices
|
||||||
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
||||||
from extras.dashboard.utils import get_widget_class
|
from extras.dashboard.utils import get_widget_class
|
||||||
from extras.utils import SharedObjectViewMixin
|
from extras.utils import SharedObjectViewMixin
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.object_actions import *
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox.views.generic.mixins import TableMixin
|
from netbox.views.generic.mixins import TableMixin
|
||||||
from utilities.forms import ConfirmationForm, get_field_value
|
from utilities.forms import ConfirmationForm, get_field_value
|
||||||
@@ -96,6 +97,11 @@ class CustomFieldBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CustomFieldBulkEditForm
|
form = forms.CustomFieldBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CustomField, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CustomFieldBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = CustomField.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CustomField, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(CustomField, 'bulk_delete', path='delete', detail=False)
|
||||||
class CustomFieldBulkDeleteView(generic.BulkDeleteView):
|
class CustomFieldBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CustomField.objects.select_related('choice_set')
|
queryset = CustomField.objects.select_related('choice_set')
|
||||||
@@ -165,6 +171,11 @@ class CustomFieldChoiceSetBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CustomFieldChoiceSetBulkEditForm
|
form = forms.CustomFieldChoiceSetBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CustomFieldChoiceSet, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CustomFieldChoiceSetBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = CustomFieldChoiceSet.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CustomFieldChoiceSet, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(CustomFieldChoiceSet, 'bulk_delete', path='delete', detail=False)
|
||||||
class CustomFieldChoiceSetBulkDeleteView(generic.BulkDeleteView):
|
class CustomFieldChoiceSetBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CustomFieldChoiceSet.objects.all()
|
queryset = CustomFieldChoiceSet.objects.all()
|
||||||
@@ -215,6 +226,11 @@ class CustomLinkBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.CustomLinkBulkEditForm
|
form = forms.CustomLinkBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CustomLink, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class CustomLinkBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = CustomLink.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CustomLink, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(CustomLink, 'bulk_delete', path='delete', detail=False)
|
||||||
class CustomLinkBulkDeleteView(generic.BulkDeleteView):
|
class CustomLinkBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CustomLink.objects.all()
|
queryset = CustomLink.objects.all()
|
||||||
@@ -232,11 +248,7 @@ class ExportTemplateListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ExportTemplateFilterSet
|
filterset = filtersets.ExportTemplateFilterSet
|
||||||
filterset_form = forms.ExportTemplateFilterForm
|
filterset_form = forms.ExportTemplateFilterForm
|
||||||
table = tables.ExportTemplateTable
|
table = tables.ExportTemplateTable
|
||||||
template_name = 'extras/exporttemplate_list.html'
|
actions = (AddObject, BulkImport, BulkSync, BulkExport, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_sync': {'sync'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ExportTemplate)
|
@register_model_view(ExportTemplate)
|
||||||
@@ -270,6 +282,11 @@ class ExportTemplateBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ExportTemplateBulkEditForm
|
form = forms.ExportTemplateBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ExportTemplate, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ExportTemplateBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ExportTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ExportTemplate, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ExportTemplate, 'bulk_delete', path='delete', detail=False)
|
||||||
class ExportTemplateBulkDeleteView(generic.BulkDeleteView):
|
class ExportTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ExportTemplate.objects.all()
|
queryset = ExportTemplate.objects.all()
|
||||||
@@ -330,6 +347,11 @@ class SavedFilterBulkEditView(SharedObjectViewMixin, generic.BulkEditView):
|
|||||||
form = forms.SavedFilterBulkEditForm
|
form = forms.SavedFilterBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SavedFilter, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class SavedFilterBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = SavedFilter.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(SavedFilter, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(SavedFilter, 'bulk_delete', path='delete', detail=False)
|
||||||
class SavedFilterBulkDeleteView(SharedObjectViewMixin, generic.BulkDeleteView):
|
class SavedFilterBulkDeleteView(SharedObjectViewMixin, generic.BulkDeleteView):
|
||||||
queryset = SavedFilter.objects.all()
|
queryset = SavedFilter.objects.all()
|
||||||
@@ -347,9 +369,7 @@ class TableConfigListView(SharedObjectViewMixin, generic.ObjectListView):
|
|||||||
filterset = filtersets.TableConfigFilterSet
|
filterset = filtersets.TableConfigFilterSet
|
||||||
filterset_form = forms.TableConfigFilterForm
|
filterset_form = forms.TableConfigFilterForm
|
||||||
table = tables.TableConfigTable
|
table = tables.TableConfigTable
|
||||||
actions = {
|
actions = (BulkExport, BulkEdit, BulkRename, BulkDelete)
|
||||||
'export': {'view'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(TableConfig)
|
@register_model_view(TableConfig)
|
||||||
@@ -389,6 +409,11 @@ class TableConfigBulkEditView(SharedObjectViewMixin, generic.BulkEditView):
|
|||||||
form = forms.TableConfigBulkEditForm
|
form = forms.TableConfigBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(TableConfig, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class TableConfigBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = TableConfig.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(TableConfig, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(TableConfig, 'bulk_delete', path='delete', detail=False)
|
||||||
class TableConfigBulkDeleteView(SharedObjectViewMixin, generic.BulkDeleteView):
|
class TableConfigBulkDeleteView(SharedObjectViewMixin, generic.BulkDeleteView):
|
||||||
queryset = TableConfig.objects.all()
|
queryset = TableConfig.objects.all()
|
||||||
@@ -470,6 +495,11 @@ class NotificationGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.NotificationGroupBulkEditForm
|
form = forms.NotificationGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(NotificationGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class NotificationGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = NotificationGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(NotificationGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(NotificationGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class NotificationGroupBulkDeleteView(generic.BulkDeleteView):
|
class NotificationGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = NotificationGroup.objects.all()
|
queryset = NotificationGroup.objects.all()
|
||||||
@@ -616,6 +646,11 @@ class WebhookBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.WebhookBulkEditForm
|
form = forms.WebhookBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Webhook, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class WebhookBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Webhook.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Webhook, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Webhook, 'bulk_delete', path='delete', detail=False)
|
||||||
class WebhookBulkDeleteView(generic.BulkDeleteView):
|
class WebhookBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Webhook.objects.all()
|
queryset = Webhook.objects.all()
|
||||||
@@ -666,6 +701,11 @@ class EventRuleBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.EventRuleBulkEditForm
|
form = forms.EventRuleBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(EventRule, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class EventRuleBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = EventRule.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(EventRule, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(EventRule, 'bulk_delete', path='delete', detail=False)
|
||||||
class EventRuleBulkDeleteView(generic.BulkDeleteView):
|
class EventRuleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = EventRule.objects.all()
|
queryset = EventRule.objects.all()
|
||||||
@@ -740,6 +780,11 @@ class TagBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.TagBulkEditForm
|
form = forms.TagBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Tag, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class TagBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Tag.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Tag, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Tag, 'bulk_delete', path='delete', detail=False)
|
||||||
class TagBulkDeleteView(generic.BulkDeleteView):
|
class TagBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
@@ -758,13 +803,7 @@ class ConfigContextListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ConfigContextFilterSet
|
filterset = filtersets.ConfigContextFilterSet
|
||||||
filterset_form = forms.ConfigContextFilterForm
|
filterset_form = forms.ConfigContextFilterForm
|
||||||
table = tables.ConfigContextTable
|
table = tables.ConfigContextTable
|
||||||
template_name = 'extras/configcontext_list.html'
|
actions = (AddObject, BulkSync, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
'add': {'add'},
|
|
||||||
'bulk_edit': {'change'},
|
|
||||||
'bulk_delete': {'delete'},
|
|
||||||
'bulk_sync': {'sync'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConfigContext)
|
@register_model_view(ConfigContext)
|
||||||
@@ -825,6 +864,11 @@ class ConfigContextBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ConfigContextBulkEditForm
|
form = forms.ConfigContextBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ConfigContext, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ConfigContextBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ConfigContext.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConfigContext, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ConfigContext, 'bulk_delete', path='delete', detail=False)
|
||||||
class ConfigContextBulkDeleteView(generic.BulkDeleteView):
|
class ConfigContextBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
@@ -877,11 +921,7 @@ class ConfigTemplateListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ConfigTemplateFilterSet
|
filterset = filtersets.ConfigTemplateFilterSet
|
||||||
filterset_form = forms.ConfigTemplateFilterForm
|
filterset_form = forms.ConfigTemplateFilterForm
|
||||||
table = tables.ConfigTemplateTable
|
table = tables.ConfigTemplateTable
|
||||||
template_name = 'extras/configtemplate_list.html'
|
actions = (AddObject, BulkImport, BulkExport, BulkSync, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_sync': {'sync'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConfigTemplate)
|
@register_model_view(ConfigTemplate)
|
||||||
@@ -915,6 +955,11 @@ class ConfigTemplateBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ConfigTemplateBulkEditForm
|
form = forms.ConfigTemplateBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ConfigTemplate, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ConfigTemplateBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ConfigTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ConfigTemplate, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ConfigTemplate, 'bulk_delete', path='delete', detail=False)
|
||||||
class ConfigTemplateBulkDeleteView(generic.BulkDeleteView):
|
class ConfigTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ConfigTemplate.objects.all()
|
queryset = ConfigTemplate.objects.all()
|
||||||
@@ -992,9 +1037,7 @@ class ImageAttachmentListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ImageAttachmentFilterSet
|
filterset = filtersets.ImageAttachmentFilterSet
|
||||||
filterset_form = forms.ImageAttachmentFilterForm
|
filterset_form = forms.ImageAttachmentFilterForm
|
||||||
table = tables.ImageAttachmentTable
|
table = tables.ImageAttachmentTable
|
||||||
actions = {
|
actions = (BulkExport,)
|
||||||
'export': {'view'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ImageAttachment, 'add', detail=False)
|
@register_model_view(ImageAttachment, 'add', detail=False)
|
||||||
@@ -1038,12 +1081,7 @@ class JournalEntryListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.JournalEntryFilterSet
|
filterset = filtersets.JournalEntryFilterSet
|
||||||
filterset_form = forms.JournalEntryFilterForm
|
filterset_form = forms.JournalEntryFilterForm
|
||||||
table = tables.JournalEntryTable
|
table = tables.JournalEntryTable
|
||||||
actions = {
|
actions = (BulkImport, BulkEdit, BulkDelete)
|
||||||
'export': {'view'},
|
|
||||||
'bulk_import': {'add'},
|
|
||||||
'bulk_edit': {'change'},
|
|
||||||
'bulk_delete': {'delete'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(JournalEntry)
|
@register_model_view(JournalEntry)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from dcim.filtersets import InterfaceFilterSet
|
|||||||
from dcim.forms import InterfaceFilterForm
|
from dcim.forms import InterfaceFilterForm
|
||||||
from dcim.models import Device, Interface, Site
|
from dcim.models import Device, Interface, Site
|
||||||
from ipam.tables import VLANTranslationRuleTable
|
from ipam.tables import VLANTranslationRuleTable
|
||||||
|
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.tables import get_table_ordering
|
from utilities.tables import get_table_ordering
|
||||||
@@ -86,6 +87,11 @@ class VRFBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VRFBulkEditForm
|
form = forms.VRFBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VRF, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VRFBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VRF.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VRF, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VRF, 'bulk_delete', path='delete', detail=False)
|
||||||
class VRFBulkDeleteView(generic.BulkDeleteView):
|
class VRFBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VRF.objects.all()
|
queryset = VRF.objects.all()
|
||||||
@@ -136,6 +142,11 @@ class RouteTargetBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RouteTargetBulkEditForm
|
form = forms.RouteTargetBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(RouteTarget, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RouteTargetBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = RouteTarget.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RouteTarget, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(RouteTarget, 'bulk_delete', path='delete', detail=False)
|
||||||
class RouteTargetBulkDeleteView(generic.BulkDeleteView):
|
class RouteTargetBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RouteTarget.objects.all()
|
queryset = RouteTarget.objects.all()
|
||||||
@@ -195,6 +206,11 @@ class RIRBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RIRBulkEditForm
|
form = forms.RIRBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(RIR, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RIRBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = RIR.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(RIR, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(RIR, 'bulk_delete', path='delete', detail=False)
|
||||||
class RIRBulkDeleteView(generic.BulkDeleteView):
|
class RIRBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RIR.objects.annotate(
|
queryset = RIR.objects.annotate(
|
||||||
@@ -268,6 +284,11 @@ class ASNRangeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ASNRangeBulkEditForm
|
form = forms.ASNRangeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ASNRange, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ASNRangeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ASNRange.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ASNRange, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ASNRange, 'bulk_delete', path='delete', detail=False)
|
||||||
class ASNRangeBulkDeleteView(generic.BulkDeleteView):
|
class ASNRangeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ASNRange.objects.annotate_asn_counts()
|
queryset = ASNRange.objects.annotate_asn_counts()
|
||||||
@@ -335,6 +356,11 @@ class ASNBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ASNBulkEditForm
|
form = forms.ASNBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ASN, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ASNBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ASN.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ASN, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ASN, 'bulk_delete', path='delete', detail=False)
|
||||||
class ASNBulkDeleteView(generic.BulkDeleteView):
|
class ASNBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ASN.objects.annotate(
|
queryset = ASN.objects.annotate(
|
||||||
@@ -356,6 +382,7 @@ class AggregateListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.AggregateFilterSet
|
filterset = filtersets.AggregateFilterSet
|
||||||
filterset_form = forms.AggregateFilterForm
|
filterset_form = forms.AggregateFilterForm
|
||||||
table = tables.AggregateTable
|
table = tables.AggregateTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Aggregate)
|
@register_model_view(Aggregate)
|
||||||
@@ -488,6 +515,11 @@ class RoleBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.RoleBulkEditForm
|
form = forms.RoleBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Role, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class RoleBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Role.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Role, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Role, 'bulk_delete', path='delete', detail=False)
|
||||||
class RoleBulkDeleteView(generic.BulkDeleteView):
|
class RoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Role.objects.all()
|
queryset = Role.objects.all()
|
||||||
@@ -506,6 +538,7 @@ class PrefixListView(generic.ObjectListView):
|
|||||||
filterset_form = forms.PrefixFilterForm
|
filterset_form = forms.PrefixFilterForm
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
template_name = 'ipam/prefix_list.html'
|
template_name = 'ipam/prefix_list.html'
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Prefix)
|
@register_model_view(Prefix)
|
||||||
@@ -766,6 +799,11 @@ class IPRangeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.IPRangeBulkEditForm
|
form = forms.IPRangeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IPRange, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class IPRangeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = IPRange.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IPRange, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(IPRange, 'bulk_delete', path='delete', detail=False)
|
||||||
class IPRangeBulkDeleteView(generic.BulkDeleteView):
|
class IPRangeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IPRange.objects.all()
|
queryset = IPRange.objects.all()
|
||||||
@@ -783,6 +821,7 @@ class IPAddressListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
filterset_form = forms.IPAddressFilterForm
|
filterset_form = forms.IPAddressFilterForm
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IPAddress)
|
@register_model_view(IPAddress)
|
||||||
@@ -1006,6 +1045,11 @@ class VLANGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VLANGroupBulkEditForm
|
form = forms.VLANGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VLANGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VLANGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VLANGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VLANGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VLANGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
|
queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags')
|
||||||
@@ -1095,6 +1139,11 @@ class VLANTranslationPolicyBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VLANTranslationPolicyBulkEditForm
|
form = forms.VLANTranslationPolicyBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VLANTranslationPolicy, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VLANTranslationPolicyBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VLANTranslationPolicy.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VLANTranslationPolicy, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VLANTranslationPolicy, 'bulk_delete', path='delete', detail=False)
|
||||||
class VLANTranslationPolicyBulkDeleteView(generic.BulkDeleteView):
|
class VLANTranslationPolicyBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VLANTranslationPolicy.objects.all()
|
queryset = VLANTranslationPolicy.objects.all()
|
||||||
@@ -1112,6 +1161,7 @@ class VLANTranslationRuleListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.VLANTranslationRuleFilterSet
|
filterset = filtersets.VLANTranslationRuleFilterSet
|
||||||
filterset_form = forms.VLANTranslationRuleFilterForm
|
filterset_form = forms.VLANTranslationRuleFilterForm
|
||||||
table = tables.VLANTranslationRuleTable
|
table = tables.VLANTranslationRuleTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VLANTranslationRule)
|
@register_model_view(VLANTranslationRule)
|
||||||
@@ -1244,6 +1294,11 @@ class FHRPGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.FHRPGroupBulkEditForm
|
form = forms.FHRPGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(FHRPGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class FHRPGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = FHRPGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(FHRPGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(FHRPGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
|
class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = FHRPGroup.objects.all()
|
queryset = FHRPGroup.objects.all()
|
||||||
@@ -1371,6 +1426,11 @@ class VLANBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VLANBulkEditForm
|
form = forms.VLANBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VLAN, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VLANBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VLAN.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VLAN, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VLAN, 'bulk_delete', path='delete', detail=False)
|
||||||
class VLANBulkDeleteView(generic.BulkDeleteView):
|
class VLANBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
@@ -1421,6 +1481,11 @@ class ServiceTemplateBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ServiceTemplateBulkEditForm
|
form = forms.ServiceTemplateBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ServiceTemplate, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ServiceTemplateBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ServiceTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ServiceTemplate, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ServiceTemplate, 'bulk_delete', path='delete', detail=False)
|
||||||
class ServiceTemplateBulkDeleteView(generic.BulkDeleteView):
|
class ServiceTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ServiceTemplate.objects.all()
|
queryset = ServiceTemplate.objects.all()
|
||||||
@@ -1488,6 +1553,11 @@ class ServiceBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ServiceBulkEditForm
|
form = forms.ServiceBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Service, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ServiceBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Service.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Service, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Service, 'bulk_delete', path='delete', detail=False)
|
||||||
class ServiceBulkDeleteView(generic.BulkDeleteView):
|
class ServiceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Service.objects.prefetch_related('parent')
|
queryset = Service.objects.prefetch_related('parent')
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ ADVISORY_LOCK_KEYS = {
|
|||||||
'job-schedules': 110100,
|
'job-schedules': 110100,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Default view action permission mapping
|
# TODO: Remove in NetBox v4.6
|
||||||
|
# Legacy default view action permission mapping
|
||||||
DEFAULT_ACTION_PERMISSIONS = {
|
DEFAULT_ACTION_PERMISSIONS = {
|
||||||
'add': {'add'},
|
'add': {'add'},
|
||||||
'export': {'view'},
|
'export': {'view'},
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ def system_job(interval):
|
|||||||
return _wrapper
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogHandler(logging.Handler):
|
||||||
|
"""
|
||||||
|
A logging handler which records entries on a Job.
|
||||||
|
"""
|
||||||
|
def __init__(self, job, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.job = job
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
# Enter the record in the log of the associated Job
|
||||||
|
self.job.log(record)
|
||||||
|
|
||||||
|
|
||||||
class JobRunner(ABC):
|
class JobRunner(ABC):
|
||||||
"""
|
"""
|
||||||
Background Job helper class.
|
Background Job helper class.
|
||||||
@@ -52,6 +65,11 @@ class JobRunner(ABC):
|
|||||||
"""
|
"""
|
||||||
self.job = job
|
self.job = job
|
||||||
|
|
||||||
|
# Initiate the system logger
|
||||||
|
self.logger = logging.getLogger(f"netbox.jobs.{self.__class__.__name__}")
|
||||||
|
self.logger.setLevel(logging.DEBUG)
|
||||||
|
self.logger.addHandler(JobLogHandler(job))
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def name(cls):
|
def name(cls):
|
||||||
return getattr(cls.Meta, 'name', cls.__name__)
|
return getattr(cls.Meta, 'name', cls.__name__)
|
||||||
|
|||||||
180
netbox/netbox/object_actions.py
Normal file
180
netbox/netbox/object_actions.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
from django.urls import reverse
|
||||||
|
from django.urls.exceptions import NoReverseMatch
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
|
from extras.models import ExportTemplate
|
||||||
|
from utilities.querydict import prepare_cloned_fields
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'AddObject',
|
||||||
|
'BulkDelete',
|
||||||
|
'BulkEdit',
|
||||||
|
'BulkExport',
|
||||||
|
'BulkImport',
|
||||||
|
'BulkRename',
|
||||||
|
'CloneObject',
|
||||||
|
'DeleteObject',
|
||||||
|
'EditObject',
|
||||||
|
'ObjectAction',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectAction:
|
||||||
|
"""
|
||||||
|
Base class for single- and multi-object operations.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
name: The action name appended to the module for view resolution
|
||||||
|
label: Human-friendly label for the rendered button
|
||||||
|
multi: Set to True if this action is performed by selecting multiple objects (i.e. using a table)
|
||||||
|
permissions_required: The set of permissions a user must have to perform the action
|
||||||
|
url_kwargs: The set of URL keyword arguments to pass when resolving the view's URL
|
||||||
|
"""
|
||||||
|
name = ''
|
||||||
|
label = None
|
||||||
|
multi = False
|
||||||
|
permissions_required = set()
|
||||||
|
url_kwargs = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_url(cls, obj):
|
||||||
|
viewname = f'{obj._meta.app_label}:{obj._meta.model_name}_{cls.name}'
|
||||||
|
kwargs = {
|
||||||
|
kwarg: getattr(obj, kwarg) for kwarg in cls.url_kwargs
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return reverse(viewname, kwargs=kwargs)
|
||||||
|
except NoReverseMatch:
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_context(cls, context, obj):
|
||||||
|
return {
|
||||||
|
'url': cls.get_url(obj),
|
||||||
|
'label': cls.label,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AddObject(ObjectAction):
|
||||||
|
"""
|
||||||
|
Create a new object.
|
||||||
|
"""
|
||||||
|
name = 'add'
|
||||||
|
label = _('Add')
|
||||||
|
permissions_required = {'add'}
|
||||||
|
template_name = 'buttons/add.html'
|
||||||
|
|
||||||
|
|
||||||
|
class CloneObject(ObjectAction):
|
||||||
|
"""
|
||||||
|
Populate the new object form with select details from an existing object.
|
||||||
|
"""
|
||||||
|
name = 'add'
|
||||||
|
label = _('Clone')
|
||||||
|
permissions_required = {'add'}
|
||||||
|
template_name = 'buttons/clone.html'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_context(cls, context, obj):
|
||||||
|
param_string = prepare_cloned_fields(obj).urlencode()
|
||||||
|
url = f'{cls.get_url(obj)}?{param_string}' if param_string else None
|
||||||
|
return {
|
||||||
|
'url': url,
|
||||||
|
'label': cls.label,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EditObject(ObjectAction):
|
||||||
|
"""
|
||||||
|
Edit a single object.
|
||||||
|
"""
|
||||||
|
name = 'edit'
|
||||||
|
label = _('Edit')
|
||||||
|
permissions_required = {'change'}
|
||||||
|
url_kwargs = ['pk']
|
||||||
|
template_name = 'buttons/edit.html'
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteObject(ObjectAction):
|
||||||
|
"""
|
||||||
|
Delete a single object.
|
||||||
|
"""
|
||||||
|
name = 'delete'
|
||||||
|
label = _('Delete')
|
||||||
|
permissions_required = {'delete'}
|
||||||
|
url_kwargs = ['pk']
|
||||||
|
template_name = 'buttons/delete.html'
|
||||||
|
|
||||||
|
|
||||||
|
class BulkImport(ObjectAction):
|
||||||
|
"""
|
||||||
|
Import multiple objects at once.
|
||||||
|
"""
|
||||||
|
name = 'bulk_import'
|
||||||
|
label = _('Import')
|
||||||
|
permissions_required = {'add'}
|
||||||
|
template_name = 'buttons/import.html'
|
||||||
|
|
||||||
|
|
||||||
|
class BulkExport(ObjectAction):
|
||||||
|
"""
|
||||||
|
Export multiple objects at once.
|
||||||
|
"""
|
||||||
|
name = 'export'
|
||||||
|
label = _('Export')
|
||||||
|
permissions_required = {'view'}
|
||||||
|
template_name = 'buttons/export.html'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_context(cls, context, model):
|
||||||
|
object_type = ObjectType.objects.get_for_model(model)
|
||||||
|
user = context['request'].user
|
||||||
|
|
||||||
|
# Determine if the "all data" export returns CSV or YAML
|
||||||
|
data_format = 'YAML' if hasattr(object_type.model_class(), 'to_yaml') else 'CSV'
|
||||||
|
|
||||||
|
# Retrieve all export templates for this model
|
||||||
|
export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'label': cls.label,
|
||||||
|
'perms': context['perms'],
|
||||||
|
'object_type': object_type,
|
||||||
|
'url_params': context['request'].GET.urlencode() if context['request'].GET else '',
|
||||||
|
'export_templates': export_templates,
|
||||||
|
'data_format': data_format,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEdit(ObjectAction):
|
||||||
|
"""
|
||||||
|
Change the value of one or more fields on a set of objects.
|
||||||
|
"""
|
||||||
|
name = 'bulk_edit'
|
||||||
|
label = _('Edit Selected')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'change'}
|
||||||
|
template_name = 'buttons/bulk_edit.html'
|
||||||
|
|
||||||
|
|
||||||
|
class BulkRename(ObjectAction):
|
||||||
|
"""
|
||||||
|
Rename multiple objects at once.
|
||||||
|
"""
|
||||||
|
name = 'bulk_rename'
|
||||||
|
label = _('Rename Selected')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'change'}
|
||||||
|
template_name = 'buttons/bulk_rename.html'
|
||||||
|
|
||||||
|
|
||||||
|
class BulkDelete(ObjectAction):
|
||||||
|
"""
|
||||||
|
Delete each of a set of objects.
|
||||||
|
"""
|
||||||
|
name = 'bulk_delete'
|
||||||
|
label = _('Delete Selected')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'delete'}
|
||||||
|
template_name = 'buttons/bulk_delete.html'
|
||||||
@@ -11,7 +11,10 @@ from core.choices import JobStatusChoices
|
|||||||
|
|
||||||
class TestJobRunner(JobRunner):
|
class TestJobRunner(JobRunner):
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
pass
|
self.logger.debug("Debug message")
|
||||||
|
self.logger.info("Info message")
|
||||||
|
self.logger.warning("Warning message")
|
||||||
|
self.logger.error("Error message")
|
||||||
|
|
||||||
|
|
||||||
class JobRunnerTestCase(TestCase):
|
class JobRunnerTestCase(TestCase):
|
||||||
@@ -47,8 +50,16 @@ class JobRunnerTest(JobRunnerTestCase):
|
|||||||
def test_handle(self):
|
def test_handle(self):
|
||||||
job = TestJobRunner.enqueue(immediate=True)
|
job = TestJobRunner.enqueue(immediate=True)
|
||||||
|
|
||||||
|
# Check job status
|
||||||
self.assertEqual(job.status, JobStatusChoices.STATUS_COMPLETED)
|
self.assertEqual(job.status, JobStatusChoices.STATUS_COMPLETED)
|
||||||
|
|
||||||
|
# Check logging
|
||||||
|
self.assertEqual(len(job.log_entries), 4)
|
||||||
|
self.assertEqual(job.log_entries[0]['message'], "Debug message")
|
||||||
|
self.assertEqual(job.log_entries[1]['message'], "Info message")
|
||||||
|
self.assertEqual(job.log_entries[2]['message'], "Warning message")
|
||||||
|
self.assertEqual(job.log_entries[3]['message'], "Error message")
|
||||||
|
|
||||||
def test_handle_errored(self):
|
def test_handle_errored(self):
|
||||||
class ErroredJobRunner(TestJobRunner):
|
class ErroredJobRunner(TestJobRunner):
|
||||||
EXP = Exception('Test error')
|
EXP = Exception('Test error')
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from core.models import ObjectType
|
|||||||
from core.signals import clear_events
|
from core.signals import clear_events
|
||||||
from extras.choices import CustomFieldUIEditableChoices
|
from extras.choices import CustomFieldUIEditableChoices
|
||||||
from extras.models import CustomField, ExportTemplate
|
from extras.models import CustomField, ExportTemplate
|
||||||
|
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
|
||||||
from utilities.error_handlers import handle_protectederror
|
from utilities.error_handlers import handle_protectederror
|
||||||
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
|
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
|
||||||
from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields
|
from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields
|
||||||
@@ -54,12 +55,12 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
|||||||
Attributes:
|
Attributes:
|
||||||
filterset: A django-filter FilterSet that is applied to the queryset
|
filterset: A django-filter FilterSet that is applied to the queryset
|
||||||
filterset_form: The form class used to render filter options
|
filterset_form: The form class used to render filter options
|
||||||
actions: A mapping of supported actions to their required permissions. When adding custom actions, bulk
|
actions: An iterable of ObjectAction subclasses (see ActionsMixin)
|
||||||
action names must be prefixed with `bulk_`. (See ActionsMixin.)
|
|
||||||
"""
|
"""
|
||||||
template_name = 'generic/object_list.html'
|
template_name = 'generic/object_list.html'
|
||||||
filterset = None
|
filterset = None
|
||||||
filterset_form = None
|
filterset_form = None
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkRename, BulkDelete)
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return get_permission_for_model(self.queryset.model, 'view')
|
return get_permission_for_model(self.queryset.model, 'view')
|
||||||
@@ -150,13 +151,13 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
|||||||
|
|
||||||
# Determine the available actions
|
# Determine the available actions
|
||||||
actions = self.get_permitted_actions(request.user)
|
actions = self.get_permitted_actions(request.user)
|
||||||
has_bulk_actions = any([a.startswith('bulk_') for a in actions])
|
has_table_actions = any(action.multi for action in actions)
|
||||||
|
|
||||||
if 'export' in request.GET:
|
if 'export' in request.GET:
|
||||||
|
|
||||||
# Export the current table view
|
# Export the current table view
|
||||||
if request.GET['export'] == 'table':
|
if request.GET['export'] == 'table':
|
||||||
table = self.get_table(self.queryset, request, has_bulk_actions)
|
table = self.get_table(self.queryset, request, has_table_actions)
|
||||||
columns = [name for name, _ in table.selected_columns]
|
columns = [name for name, _ in table.selected_columns]
|
||||||
return self.export_table(table, columns)
|
return self.export_table(table, columns)
|
||||||
|
|
||||||
@@ -174,11 +175,11 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
|||||||
|
|
||||||
# Fall back to default table/YAML export
|
# Fall back to default table/YAML export
|
||||||
else:
|
else:
|
||||||
table = self.get_table(self.queryset, request, has_bulk_actions)
|
table = self.get_table(self.queryset, request, has_table_actions)
|
||||||
return self.export_table(table)
|
return self.export_table(table)
|
||||||
|
|
||||||
# Render the objects table
|
# Render the objects table
|
||||||
table = self.get_table(self.queryset, request, has_bulk_actions)
|
table = self.get_table(self.queryset, request, has_table_actions)
|
||||||
|
|
||||||
# If this is an HTMX request, return only the rendered table HTML
|
# If this is an HTMX request, return only the rendered table HTML
|
||||||
if htmx_partial(request):
|
if htmx_partial(request):
|
||||||
@@ -729,7 +730,11 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
||||||
"""
|
"""
|
||||||
An extendable view for renaming objects in bulk.
|
An extendable view for renaming objects in bulk.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
field_name: The name of the object attribute for which the value is being updated (defaults to "name")
|
||||||
"""
|
"""
|
||||||
|
field_name = 'name'
|
||||||
template_name = 'generic/bulk_rename.html'
|
template_name = 'generic/bulk_rename.html'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -759,12 +764,12 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
replace = form.cleaned_data['replace']
|
replace = form.cleaned_data['replace']
|
||||||
if form.cleaned_data['use_regex']:
|
if form.cleaned_data['use_regex']:
|
||||||
try:
|
try:
|
||||||
obj.new_name = re.sub(find, replace, obj.name or '')
|
obj.new_name = re.sub(find, replace, getattr(obj, self.field_name, ''))
|
||||||
# Catch regex group reference errors
|
# Catch regex group reference errors
|
||||||
except re.error:
|
except re.error:
|
||||||
obj.new_name = obj.name
|
obj.new_name = getattr(obj, self.field_name)
|
||||||
else:
|
else:
|
||||||
obj.new_name = (obj.name or '').replace(find, replace)
|
obj.new_name = getattr(obj, self.field_name, '').replace(find, replace)
|
||||||
renamed_pks.append(obj.pk)
|
renamed_pks.append(obj.pk)
|
||||||
|
|
||||||
return renamed_pks
|
return renamed_pks
|
||||||
@@ -783,7 +788,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
|
|
||||||
if '_apply' in request.POST:
|
if '_apply' in request.POST:
|
||||||
for obj in selected_objects:
|
for obj in selected_objects:
|
||||||
obj.name = obj.new_name
|
setattr(obj, self.field_name, obj.new_name)
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
# Enforce constrained permissions
|
# Enforce constrained permissions
|
||||||
@@ -813,6 +818,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
|
selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
|
'field_name': self.field_name,
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
|
'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
|
||||||
'selected_objects': selected_objects,
|
'selected_objects': selected_objects,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from extras.models import TableConfig
|
from extras.models import TableConfig
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox import object_actions
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@@ -9,6 +9,18 @@ __all__ = (
|
|||||||
'TableMixin',
|
'TableMixin',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.5
|
||||||
|
LEGACY_ACTIONS = {
|
||||||
|
'add': object_actions.AddObject,
|
||||||
|
'edit': object_actions.EditObject,
|
||||||
|
'delete': object_actions.DeleteObject,
|
||||||
|
'export': object_actions.BulkExport,
|
||||||
|
'bulk_import': object_actions.BulkImport,
|
||||||
|
'bulk_edit': object_actions.BulkEdit,
|
||||||
|
'bulk_rename': object_actions.BulkRename,
|
||||||
|
'bulk_delete': object_actions.BulkDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ActionsMixin:
|
class ActionsMixin:
|
||||||
"""
|
"""
|
||||||
@@ -19,7 +31,24 @@ class ActionsMixin:
|
|||||||
Standard actions include: add, import, export, bulk_edit, and bulk_delete. Some views extend this default map
|
Standard actions include: add, import, export, bulk_edit, and bulk_delete. Some views extend this default map
|
||||||
with custom actions, such as bulk_sync.
|
with custom actions, such as bulk_sync.
|
||||||
"""
|
"""
|
||||||
actions = DEFAULT_ACTION_PERMISSIONS
|
actions = tuple()
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.5
|
||||||
|
def _convert_legacy_actions(self):
|
||||||
|
"""
|
||||||
|
Convert a legacy dictionary mapping action name to required permissions to a list of ObjectAction subclasses.
|
||||||
|
"""
|
||||||
|
if type(self.actions) is not dict:
|
||||||
|
return
|
||||||
|
|
||||||
|
actions = []
|
||||||
|
for name in self.actions.keys():
|
||||||
|
try:
|
||||||
|
actions.append(LEGACY_ACTIONS[name])
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(f"Unsupported legacy action: {name}")
|
||||||
|
|
||||||
|
self.actions = actions
|
||||||
|
|
||||||
def get_permitted_actions(self, user, model=None):
|
def get_permitted_actions(self, user, model=None):
|
||||||
"""
|
"""
|
||||||
@@ -27,11 +56,15 @@ class ActionsMixin:
|
|||||||
"""
|
"""
|
||||||
model = model or self.queryset.model
|
model = model or self.queryset.model
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.5
|
||||||
|
# Handle legacy action sets
|
||||||
|
self._convert_legacy_actions()
|
||||||
|
|
||||||
# Resolve required permissions for each action
|
# Resolve required permissions for each action
|
||||||
permitted_actions = []
|
permitted_actions = []
|
||||||
for action in self.actions:
|
for action in self.actions:
|
||||||
required_permissions = [
|
required_permissions = [
|
||||||
get_permission_for_model(model, name) for name in self.actions.get(action, set())
|
get_permission_for_model(model, perm) for perm in action.permissions_required
|
||||||
]
|
]
|
||||||
if not required_permissions or user.has_perms(required_permissions):
|
if not required_permissions or user.has_perms(required_permissions):
|
||||||
permitted_actions.append(action)
|
permitted_actions.append(action)
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from core.signals import clear_events
|
from core.signals import clear_events
|
||||||
|
from netbox.object_actions import (
|
||||||
|
AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, CloneObject, DeleteObject, EditObject,
|
||||||
|
)
|
||||||
from utilities.error_handlers import handle_protectederror
|
from utilities.error_handlers import handle_protectederror
|
||||||
from utilities.exceptions import AbortRequest, PermissionsViolation
|
from utilities.exceptions import AbortRequest, PermissionsViolation
|
||||||
from utilities.forms import ConfirmationForm, restrict_form_fields
|
from utilities.forms import ConfirmationForm, restrict_form_fields
|
||||||
@@ -36,7 +39,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ObjectView(BaseObjectView):
|
class ObjectView(ActionsMixin, BaseObjectView):
|
||||||
"""
|
"""
|
||||||
Retrieve a single object for display.
|
Retrieve a single object for display.
|
||||||
|
|
||||||
@@ -44,8 +47,10 @@ class ObjectView(BaseObjectView):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
tab: A ViewTab instance for the view
|
tab: A ViewTab instance for the view
|
||||||
|
actions: An iterable of ObjectAction subclasses (see ActionsMixin)
|
||||||
"""
|
"""
|
||||||
tab = None
|
tab = None
|
||||||
|
actions = (CloneObject, EditObject, DeleteObject)
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return get_permission_for_model(self.queryset.model, 'view')
|
return get_permission_for_model(self.queryset.model, 'view')
|
||||||
@@ -72,9 +77,11 @@ class ObjectView(BaseObjectView):
|
|||||||
request: The current request
|
request: The current request
|
||||||
"""
|
"""
|
||||||
instance = self.get_object(**kwargs)
|
instance = self.get_object(**kwargs)
|
||||||
|
actions = self.get_permitted_actions(request.user, model=instance)
|
||||||
|
|
||||||
return render(request, self.get_template_name(), {
|
return render(request, self.get_template_name(), {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
|
'actions': actions,
|
||||||
'tab': self.tab,
|
'tab': self.tab,
|
||||||
**self.get_extra_context(request, instance),
|
**self.get_extra_context(request, instance),
|
||||||
})
|
})
|
||||||
@@ -90,13 +97,13 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
|||||||
table: The django-tables2 Table class used to render the child objects list
|
table: The django-tables2 Table class used to render the child objects list
|
||||||
filterset: A django-filter FilterSet that is applied to the queryset
|
filterset: A django-filter FilterSet that is applied to the queryset
|
||||||
filterset_form: The form class used to render filter options
|
filterset_form: The form class used to render filter options
|
||||||
actions: A mapping of supported actions to their required permissions. When adding custom actions, bulk
|
actions: An iterable of ObjectAction subclasses (see ActionsMixin)
|
||||||
action names must be prefixed with `bulk_`. (See ActionsMixin.)
|
|
||||||
"""
|
"""
|
||||||
child_model = None
|
child_model = None
|
||||||
table = None
|
table = None
|
||||||
filterset = None
|
filterset = None
|
||||||
filterset_form = None
|
filterset_form = None
|
||||||
|
actions = (AddObject, BulkImport, BulkEdit, BulkExport, BulkDelete)
|
||||||
template_name = 'generic/object_children.html'
|
template_name = 'generic/object_children.html'
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
@@ -138,10 +145,10 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
|||||||
|
|
||||||
# Determine the available actions
|
# Determine the available actions
|
||||||
actions = self.get_permitted_actions(request.user, model=self.child_model)
|
actions = self.get_permitted_actions(request.user, model=self.child_model)
|
||||||
has_bulk_actions = any([a.startswith('bulk_') for a in actions])
|
has_table_actions = any(action.multi for action in actions)
|
||||||
|
|
||||||
table_data = self.prep_table_data(request, child_objects, instance)
|
table_data = self.prep_table_data(request, child_objects, instance)
|
||||||
table = self.get_table(table_data, request, has_bulk_actions)
|
table = self.get_table(table_data, request, has_table_actions)
|
||||||
|
|
||||||
# If this is an HTMX request, return only the rendered table HTML
|
# If this is an HTMX request, return only the rendered table HTML
|
||||||
if htmx_partial(request):
|
if htmx_partial(request):
|
||||||
|
|||||||
3
netbox/templates/core/buttons/bulk_sync.html
Normal file
3
netbox/templates/core/buttons/bulk_sync.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<button type="submit" name="_sync" {% formaction %}="{{ url }}" class="btn btn-primary">
|
||||||
|
<i class="mdi mdi-sync" aria-hidden="true"></i> {{ label }}
|
||||||
|
</button>
|
||||||
@@ -11,12 +11,6 @@
|
|||||||
<li class="breadcrumb-item"><a href="{% url 'core:datafile_list' %}?source_id={{ object.source.pk }}">{{ object.source }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'core:datafile_list' %}?source_id={{ object.source.pk }}">{{ object.source }}</a></li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block control-buttons %}
|
|
||||||
{% if request.user|can_delete:object %}
|
|
||||||
{% delete_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock control-buttons %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|||||||
@@ -1,33 +1,6 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'core/job/base.html' %}
|
||||||
{% load buttons %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load perms %}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if object.object %}
|
|
||||||
<li class="breadcrumb-item">
|
|
||||||
<a href="{% url 'core:job_list' %}?object_type={{ object.object_type_id }}">{{ object.object|meta:"verbose_name_plural"|bettertitle }}</a>
|
|
||||||
</li>
|
|
||||||
{% with parent_jobs_viewname=object.object|viewname:"jobs" %}
|
|
||||||
<li class="breadcrumb-item">
|
|
||||||
<a href="{% url parent_jobs_viewname pk=object.object.pk %}">{{ object.object }}</a>
|
|
||||||
</li>
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
<li class="breadcrumb-item">
|
|
||||||
<a href="{% url 'core:job_list' %}?name={{ object.name|urlencode }}">{{ object.name }}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock breadcrumbs %}
|
|
||||||
|
|
||||||
{% block control-buttons %}
|
|
||||||
{% if request.user|can_delete:object %}
|
|
||||||
{% delete_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock control-buttons %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col col-12 col-md-6">
|
<div class="col col-12 col-md-6">
|
||||||
|
|||||||
23
netbox/templates/core/job/base.html
Normal file
23
netbox/templates/core/job/base.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load perms %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% if object.object %}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'core:job_list' %}?object_type={{ object.object_type_id }}">{{ object.object|meta:"verbose_name_plural"|bettertitle }}</a>
|
||||||
|
</li>
|
||||||
|
{% with parent_jobs_viewname=object.object|viewname:"jobs" %}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url parent_jobs_viewname pk=object.object.pk %}">{{ object.object }}</a>
|
||||||
|
</li>
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'core:job_list' %}?name={{ object.name|urlencode }}">{{ object.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock breadcrumbs %}
|
||||||
12
netbox/templates/core/job/log.html
Normal file
12
netbox/templates/core/job/log.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'core/job/base.html' %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
{% render_table table %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
71
netbox/templates/dcim/buttons/bulk_add_components.html
Normal file
71
netbox/templates/dcim/buttons/bulk_add_components.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {{ label }}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% if perms.dcim.add_consoleport %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_consoleport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Console Ports" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_consoleserverport %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_consoleserverport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item ">
|
||||||
|
{% trans "Console Server Ports" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_powerport %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_powerport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Power Ports" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_poweroutlet %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_poweroutlet' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Power Outlets" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_interface %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_interface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Interfaces" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_rearport %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_rearport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Rear Ports" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_devicebay %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_devicebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Device Bays" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_modulebay %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_modulebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Module Bays" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.dcim.add_inventoryitem %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_inventoryitem' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Inventory Items" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
3
netbox/templates/dcim/buttons/bulk_disconnect.html
Normal file
3
netbox/templates/dcim/buttons/bulk_disconnect.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<button type="submit" name="_disconnect" {% formaction %}="{{ url }}" class="btn btn-red">
|
||||||
|
<i class="mdi mdi-ethernet-cable-off" aria-hidden="true"></i> {{ label }}
|
||||||
|
</button>
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load buttons %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_buttons %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
{% bulk_edit_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_rename' in actions %}
|
|
||||||
{% with bulk_rename_view=model|validated_viewname:"bulk_rename" %}
|
|
||||||
<button type="submit" name="_rename" {% formaction %}="{% url bulk_rename_view %}" class="btn btn-outline-warning btn-float">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename Selected" %}
|
|
||||||
</button>
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
{% bulk_delete_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{% extends 'generic/object_children.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
|
|
||||||
{% block bulk_edit_controls %}
|
|
||||||
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
|
|
||||||
{% if 'bulk_edit' in actions and bulk_edit_view %}
|
|
||||||
<button type="submit" name="_edit"
|
|
||||||
{% formaction %}="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
|
|
||||||
class="btn btn-warning">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
|
|
||||||
{% if 'bulk_rename' in actions and bulk_rename_view %}
|
|
||||||
<button type="submit" name="_rename"
|
|
||||||
{% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-warning">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_edit_controls %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_consoleport %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Console Ports" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Console Server Ports" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_devicebay %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Device Bays" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_frontport %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Front Ports" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,30 +1,5 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
{% extends 'generic/object_children.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block table_controls %}
|
{% block table_controls %}
|
||||||
{% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %}
|
{% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %}
|
||||||
{% endblock table_controls %}
|
{% endblock table_controls %}
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_interface %}
|
|
||||||
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Interfaces" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_inventoryitem %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Inventory Item" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_modulebay %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Module Bays" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Power Outlets" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_powerport %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Power Port" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends 'dcim/device/components_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
|
|
||||||
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
|
|
||||||
<button type="submit" name="_disconnect"
|
|
||||||
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-danger">
|
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
|
|
||||||
{% block bulk_extra_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if perms.dcim.add_rearport %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}"
|
|
||||||
class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Rear Ports" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_extra_controls %}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load buttons %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_buttons %}
|
|
||||||
{% if perms.dcim.change_device %}
|
|
||||||
<div class="dropdown">
|
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Components" %}
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{% if perms.dcim.add_consoleport %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_consoleport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Console Ports" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_consoleserverport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item ">
|
|
||||||
{% trans "Console Server Ports" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_powerport %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_powerport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Power Ports" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_poweroutlet' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Power Outlets" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_interface %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_interface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Interfaces" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_rearport %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_rearport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Rear Ports" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_devicebay %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_devicebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Device Bays" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_modulebay %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_modulebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Module Bays" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_inventoryitem %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_inventoryitem' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Inventory Items" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_edit' in actions %}
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
{% bulk_edit_button model query_params=request.GET %}
|
|
||||||
<button type="submit" name="_rename" {% formaction %}="{% url 'dcim:device_bulk_rename' %}?return_url={% url 'dcim:device_list' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-outline-warning btn-float">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
{% bulk_delete_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{% extends 'generic/object_children.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load perms %}
|
|
||||||
|
|
||||||
{% block bulk_edit_controls %}
|
|
||||||
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
|
|
||||||
{% if 'bulk_edit' in actions and bulk_edit_view %}
|
|
||||||
<button type="submit" name="_edit"
|
|
||||||
{% formaction %}="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
|
|
||||||
class="btn btn-warning">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
|
|
||||||
{% if 'bulk_rename' in actions and bulk_rename_view %}
|
|
||||||
<button type="submit" name="_rename"
|
|
||||||
{% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-warning">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_edit_controls %}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{% extends 'generic/object_children.html' %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block extra_controls %}
|
|
||||||
{% include 'dcim/inc/moduletype_buttons.html' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block bulk_edit_controls %}
|
|
||||||
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
|
|
||||||
{% if 'bulk_edit' in actions and bulk_edit_view %}
|
|
||||||
<button type="submit" name="_edit"
|
|
||||||
{% formaction %}="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
|
|
||||||
class="btn btn-warning">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
|
|
||||||
{% if 'bulk_rename' in actions and bulk_rename_view %}
|
|
||||||
<button type="submit" name="_rename"
|
|
||||||
{% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-warning">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_edit_controls %}
|
|
||||||
|
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'generic/object.html' %}
|
||||||
{% load buttons %}
|
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block buttons %}
|
|
||||||
{% if perms.dcim.change_virtualchassis %}
|
|
||||||
{% edit_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.delete_virtualchassis %}
|
|
||||||
{% delete_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-12 col-md-4">
|
<div class="col col-12 col-md-4">
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_buttons %}
|
|
||||||
{% if perms.extras.sync_configcontext %}
|
|
||||||
<button type="submit" name="_sync" {% formaction %}="{% url 'extras:configcontext_bulk_sync' %}" class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync Data" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_buttons %}
|
|
||||||
{% if perms.extras.sync_configtemplate %}
|
|
||||||
<button type="submit" name="_sync" {% formaction %}="{% url 'extras:configtemplate_bulk_sync' %}" class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync Data" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_buttons %}
|
|
||||||
{% if perms.extras.sync_configcontext %}
|
|
||||||
<button type="submit" name="_sync" {% formaction %}="{% url 'extras:exporttemplate_bulk_sync' %}" class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync Data" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{% extends 'generic/_base.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% comment %}
|
|
||||||
Blocks:
|
|
||||||
- title: Page title
|
|
||||||
- tabs: Page tabs
|
|
||||||
- content: Primary page content
|
|
||||||
|
|
||||||
Context:
|
|
||||||
- form: The bulk edit form class
|
|
||||||
- parent_obj: The parent object
|
|
||||||
- table: A table of objects being removed
|
|
||||||
- obj_type_plural: The plural form of the object type
|
|
||||||
- return_url: The URL to which the user is redirected after submitting the form
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% trans "Remove" %} {{ table.rows|length }} {{ obj_type_plural|bettertitle }}?
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link active" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
|
||||||
{% trans "Bulk Remove" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{% endblock tabs %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="tab-pane show active" role="tabpanel">
|
|
||||||
<div class="alert alert-danger bg-danger-subtle" role="alert">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div>
|
|
||||||
<i class="mdi mdi-alert-octagon p-2"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 class="alert-title">{% trans "Confirm Bulk Removal" %}</h4>
|
|
||||||
{% blocktrans trimmed with count=table.rows|length %}
|
|
||||||
The following operation will remove {{ count }} {{ obj_type_plural }} from {{ parent_obj }}. Please
|
|
||||||
carefully review the {{ obj_type_plural }} to be removed and confirm below.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container-fluid px-0">
|
|
||||||
<div class="card">
|
|
||||||
<div class="table-responsive">
|
|
||||||
{% render_table table 'inc/table.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form action="." method="post" class="form">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% for field in form.hidden_fields %}
|
|
||||||
{{ field }}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="text-end">
|
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
|
|
||||||
<button type="submit" name="_confirm" class="btn btn-danger">
|
|
||||||
{% blocktrans trimmed with count=table.rows|length %}
|
|
||||||
Remove these {{ count }} {{ obj_type_plural }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
|
||||||
@@ -42,10 +42,12 @@ Context:
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for obj in selected_objects %}
|
{% for obj in selected_objects %}
|
||||||
<tr{% if obj.new_name and obj.name != obj.new_name %} class="success"{% endif %}>
|
{% with obj_name=obj|getattr:field_name %}
|
||||||
<td>{{ obj.name }}</td>
|
<tr{% if obj.new_name and obj_name != obj.new_name %} class="success"{% endif %}>
|
||||||
<td>{{ obj.new_name }}</td>
|
<td>{{ obj_name }}</td>
|
||||||
</tr>
|
<td>{{ obj.new_name }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -80,15 +80,7 @@ Context:
|
|||||||
{% if perms.extras.add_subscription and object.subscriptions %}
|
{% if perms.extras.add_subscription and object.subscriptions %}
|
||||||
{% subscribe_button object %}
|
{% subscribe_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user|can_add:object %}
|
{% action_buttons actions object %}
|
||||||
{% clone_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% if request.user|can_change:object %}
|
|
||||||
{% edit_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% if request.user|can_delete:object %}
|
|
||||||
{% delete_button object %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock control-buttons %}
|
{% endblock control-buttons %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends base_template %}
|
{% extends base_template %}
|
||||||
|
{% load buttons %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
@@ -7,8 +8,6 @@ Blocks:
|
|||||||
- content: Primary page content
|
- content: Primary page content
|
||||||
- table_controls: Control elements for the child objects table
|
- table_controls: Control elements for the child objects table
|
||||||
- bulk_controls: Bulk action buttons which appear beneath the child objects table
|
- bulk_controls: Bulk action buttons which appear beneath the child objects table
|
||||||
- bulk_edit_controls: Bulk edit buttons
|
|
||||||
- bulk_delete_controls: Bulk delete buttons
|
|
||||||
- bulk_extra_controls: Other bulk action buttons
|
- bulk_extra_controls: Other bulk action buttons
|
||||||
- modals: Any pre-loaded modals
|
- modals: Any pre-loaded modals
|
||||||
|
|
||||||
@@ -36,36 +35,8 @@ Context:
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-print-none mt-2">
|
<div class="d-print-none mt-2">
|
||||||
{% block bulk_controls %}
|
{% block bulk_controls %}
|
||||||
<div class="btn-group" role="group">
|
{% action_buttons actions model multi=True %}
|
||||||
{# Bulk edit buttons #}
|
{% block bulk_extra_controls %}{% endblock %}
|
||||||
{% block bulk_edit_controls %}
|
|
||||||
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
|
|
||||||
{% if 'bulk_edit' in actions and bulk_edit_view %}
|
|
||||||
<button type="submit" name="_edit"
|
|
||||||
{% formaction %}="{% url bulk_edit_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-warning">
|
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit Selected" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_edit_controls %}
|
|
||||||
</div>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
{# Bulk delete buttons #}
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{% with bulk_delete_view=child_model|validated_viewname:"bulk_delete" %}
|
|
||||||
{% if 'bulk_delete' in actions and bulk_delete_view %}
|
|
||||||
<button type="submit"
|
|
||||||
{% formaction %}="{% url bulk_delete_view %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-danger">
|
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete Selected" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
</div>
|
|
||||||
{# Other bulk action buttons #}
|
|
||||||
{% block bulk_extra_controls %}{% endblock %}
|
|
||||||
{% endblock bulk_controls %}
|
{% endblock bulk_controls %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -31,15 +31,7 @@ Context:
|
|||||||
<div class="btn-list">
|
<div class="btn-list">
|
||||||
{% plugin_list_buttons model %}
|
{% plugin_list_buttons model %}
|
||||||
{% block extra_controls %}{% endblock %}
|
{% block extra_controls %}{% endblock %}
|
||||||
{% if 'add' in actions %}
|
{% action_buttons actions model %}
|
||||||
{% add_button model %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_import' in actions %}
|
|
||||||
{% import_button model %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'export' in actions %}
|
|
||||||
{% export_button model %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock controls %}
|
{% endblock controls %}
|
||||||
|
|
||||||
@@ -91,12 +83,7 @@ Context:
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="bulk-action-buttons">
|
<div class="bulk-action-buttons">
|
||||||
{% if 'bulk_edit' in actions %}
|
{% action_buttons actions model multi=True %}
|
||||||
{% bulk_edit_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
{% bulk_delete_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,12 +111,7 @@ Context:
|
|||||||
<div class="btn-list d-print-none">
|
<div class="btn-list d-print-none">
|
||||||
{% block bulk_buttons %}
|
{% block bulk_buttons %}
|
||||||
<div class="bulk-action-buttons">
|
<div class="bulk-action-buttons">
|
||||||
{% if 'bulk_edit' in actions %}
|
{% action_buttons actions model multi=True %}
|
||||||
{% bulk_edit_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
{% bulk_delete_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,12 +27,7 @@
|
|||||||
{# Update the bulk action buttons with new query parameters #}
|
{# Update the bulk action buttons with new query parameters #}
|
||||||
{% if actions %}
|
{% if actions %}
|
||||||
<div class="bulk-action-buttons" hx-swap-oob="outerHTML:.bulk-action-buttons">
|
<div class="bulk-action-buttons" hx-swap-oob="outerHTML:.bulk-action-buttons">
|
||||||
{% if 'bulk_edit' in actions %}
|
{% action_buttons actions model multi=True %}
|
||||||
{% bulk_edit_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
{% if 'bulk_delete' in actions %}
|
|
||||||
{% bulk_delete_button model query_params=request.GET %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Components" %}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% if perms.virtualization.add_vminterface %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'virtualization:virtualmachine_bulk_add_vminterface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Interfaces" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.virtualization.add_virtualdisk %}
|
||||||
|
<li>
|
||||||
|
<button type="submit" {% formaction %}="{% url 'virtualization:virtualmachine_bulk_add_virtualdisk' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
||||||
|
{% trans "Virtual Disks" %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{% extends 'generic/object_children.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_delete_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if 'bulk_remove_devices' in actions %}
|
|
||||||
<button type="submit" name="_remove"
|
|
||||||
{% formaction %}="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-danger">
|
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> {% trans "Remove Selected" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_delete_controls %}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{% extends 'generic/object_children.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_edit_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if 'bulk_rename' in actions %}
|
|
||||||
<button type="submit" name="_rename"
|
|
||||||
{% formaction %}="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-warning">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_edit_controls %}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{% extends 'generic/object_children.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_edit_controls %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if 'bulk_rename' in actions %}
|
|
||||||
<button type="submit" name="_rename"
|
|
||||||
{% formaction %}="{% url 'virtualization:virtualdisk_bulk_rename' %}?return_url={{ return_url }}"
|
|
||||||
class="btn btn-outline-warning">
|
|
||||||
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock bulk_edit_controls %}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block bulk_buttons %}
|
|
||||||
{% if perms.virtualization.change_virtualmachine %}
|
|
||||||
<div class="dropdown">
|
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add Components" %}
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{% if perms.virtualization.add_vminterface %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'virtualization:virtualmachine_bulk_add_vminterface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Interfaces" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.virtualization.add_virtualdisk %}
|
|
||||||
<li>
|
|
||||||
<button type="submit" {% formaction %}="{% url 'virtualization:virtualmachine_bulk_add_virtualdisk' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
|
|
||||||
{% trans "Virtual Disks" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from netbox.object_actions import BulkDelete, BulkEdit, BulkExport, BulkImport
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.views import GetRelatedModelsMixin, register_model_view
|
from utilities.views import GetRelatedModelsMixin, register_model_view
|
||||||
@@ -70,6 +71,11 @@ class TenantGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.TenantGroupBulkEditForm
|
form = forms.TenantGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(TenantGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class TenantGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = TenantGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(TenantGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(TenantGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class TenantGroupBulkDeleteView(generic.BulkDeleteView):
|
class TenantGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = TenantGroup.objects.add_related_count(
|
queryset = TenantGroup.objects.add_related_count(
|
||||||
@@ -131,6 +137,11 @@ class TenantBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.TenantBulkEditForm
|
form = forms.TenantBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Tenant, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class TenantBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Tenant.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Tenant, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Tenant, 'bulk_delete', path='delete', detail=False)
|
||||||
class TenantBulkDeleteView(generic.BulkDeleteView):
|
class TenantBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Tenant.objects.all()
|
queryset = Tenant.objects.all()
|
||||||
@@ -206,6 +217,11 @@ class ContactGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ContactGroupBulkEditForm
|
form = forms.ContactGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ContactGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ContactGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ContactGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ContactGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ContactGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class ContactGroupBulkDeleteView(generic.BulkDeleteView):
|
class ContactGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ContactGroup.objects.add_related_count(
|
queryset = ContactGroup.objects.add_related_count(
|
||||||
@@ -267,6 +283,11 @@ class ContactRoleBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ContactRoleBulkEditForm
|
form = forms.ContactRoleBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ContactRole, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ContactRoleBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ContactRole.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ContactRole, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ContactRole, 'bulk_delete', path='delete', detail=False)
|
||||||
class ContactRoleBulkDeleteView(generic.BulkDeleteView):
|
class ContactRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ContactRole.objects.all()
|
queryset = ContactRole.objects.all()
|
||||||
@@ -330,6 +351,11 @@ class ContactBulkEditView(generic.BulkEditView):
|
|||||||
obj.groups.remove(*form.cleaned_data['remove_groups'])
|
obj.groups.remove(*form.cleaned_data['remove_groups'])
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Contact, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ContactBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Contact.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Contact, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Contact, 'bulk_delete', path='delete', detail=False)
|
||||||
class ContactBulkDeleteView(generic.BulkDeleteView):
|
class ContactBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Contact.objects.annotate(
|
queryset = Contact.objects.annotate(
|
||||||
@@ -349,12 +375,7 @@ class ContactAssignmentListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ContactAssignmentFilterSet
|
filterset = filtersets.ContactAssignmentFilterSet
|
||||||
filterset_form = forms.ContactAssignmentFilterForm
|
filterset_form = forms.ContactAssignmentFilterForm
|
||||||
table = tables.ContactAssignmentTable
|
table = tables.ContactAssignmentTable
|
||||||
actions = {
|
actions = (BulkExport, BulkImport, BulkEdit, BulkDelete)
|
||||||
'export': {'view'},
|
|
||||||
'bulk_import': {'add'},
|
|
||||||
'bulk_edit': {'change'},
|
|
||||||
'bulk_delete': {'delete'},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ContactAssignment, 'add', detail=False)
|
@register_model_view(ContactAssignment, 'add', detail=False)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from django.db.models import Count
|
|||||||
|
|
||||||
from core.models import ObjectChange
|
from core.models import ObjectChange
|
||||||
from core.tables import ObjectChangeTable
|
from core.tables import ObjectChangeTable
|
||||||
|
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.views import register_model_view
|
from utilities.views import register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
@@ -18,6 +19,7 @@ class TokenListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.TokenFilterSet
|
filterset = filtersets.TokenFilterSet
|
||||||
filterset_form = forms.TokenFilterForm
|
filterset_form = forms.TokenFilterForm
|
||||||
table = tables.TokenTable
|
table = tables.TokenTable
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Token)
|
@register_model_view(Token)
|
||||||
@@ -111,6 +113,12 @@ class UserBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.UserBulkEditForm
|
form = forms.UserBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(User, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class UserBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = User.objects.all()
|
||||||
|
field_name = 'username'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(User, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(User, 'bulk_delete', path='delete', detail=False)
|
||||||
class UserBulkDeleteView(generic.BulkDeleteView):
|
class UserBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
@@ -162,6 +170,11 @@ class GroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.GroupBulkEditForm
|
form = forms.GroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Group, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class GroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Group.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Group, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Group, 'bulk_delete', path='delete', detail=False)
|
||||||
class GroupBulkDeleteView(generic.BulkDeleteView):
|
class GroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Group.objects.annotate(users_count=Count('user')).order_by('name')
|
queryset = Group.objects.annotate(users_count=Count('user')).order_by('name')
|
||||||
@@ -179,6 +192,7 @@ class ObjectPermissionListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.ObjectPermissionFilterSet
|
filterset = filtersets.ObjectPermissionFilterSet
|
||||||
filterset_form = forms.ObjectPermissionFilterForm
|
filterset_form = forms.ObjectPermissionFilterForm
|
||||||
table = tables.ObjectPermissionTable
|
table = tables.ObjectPermissionTable
|
||||||
|
actions = (AddObject, BulkExport, BulkEdit, BulkRename, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ObjectPermission)
|
@register_model_view(ObjectPermission)
|
||||||
@@ -207,6 +221,11 @@ class ObjectPermissionBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ObjectPermissionBulkEditForm
|
form = forms.ObjectPermissionBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ObjectPermission, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ObjectPermissionBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ObjectPermission.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ObjectPermission, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ObjectPermission, 'bulk_delete', path='delete', detail=False)
|
||||||
class ObjectPermissionBulkDeleteView(generic.BulkDeleteView):
|
class ObjectPermissionBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ObjectPermission.objects.all()
|
queryset = ObjectPermission.objects.all()
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import decimal
|
import decimal
|
||||||
|
import json
|
||||||
|
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
|
||||||
|
from utilities.datetime import datetime_from_timestamp
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConfigJSONEncoder',
|
'ConfigJSONEncoder',
|
||||||
'CustomFieldJSONEncoder',
|
'CustomFieldJSONEncoder',
|
||||||
|
'JobLogDecoder',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,3 +33,21 @@ class ConfigJSONEncoder(DjangoJSONEncoder):
|
|||||||
return type(o).__name__
|
return type(o).__name__
|
||||||
|
|
||||||
return super().default(o)
|
return super().default(o)
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogDecoder(json.JSONDecoder):
|
||||||
|
"""
|
||||||
|
Deserialize JobLogEntry timestamps.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['object_hook'] = self._deserialize_entry
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _deserialize_entry(self, obj: dict) -> dict:
|
||||||
|
if obj.get('timestamp'):
|
||||||
|
# Deserialize a timestamp string to a native datetime object
|
||||||
|
try:
|
||||||
|
obj['timestamp'] = datetime_from_timestamp(obj['timestamp'])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return obj
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
{% if url %}
|
<a href="{{ url }}" class="btn btn-primary" role="button">
|
||||||
{% load i18n %}
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {{ label }}
|
||||||
<a href="{{ url }}" type="button" class="btn btn-primary">
|
</a>
|
||||||
<i class="mdi mdi-plus-thick"></i> {% trans "Add" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
{% load i18n %}
|
<button type="submit" name="_delete" {% formaction %}="{{ url }}" class="btn btn-red">
|
||||||
{% if url %}
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {{ label }}
|
||||||
<button type="submit" name="_delete" {% formaction %}="{{ url }}" class="btn btn-red">
|
</button>
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete Selected" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
{% load i18n %}
|
<button type="submit" name="_edit" {% formaction %}="{{ url }}" class="btn btn-yellow">
|
||||||
{% if url %}
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> {{ label }}
|
||||||
<button type="submit" name="_edit" {% formaction %}="{{ url }}" class="btn btn-yellow">
|
</button>
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit Selected" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
|
|||||||
5
netbox/utilities/templates/buttons/bulk_rename.html
Normal file
5
netbox/utilities/templates/buttons/bulk_rename.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% if url %}
|
||||||
|
<button type="submit" name="_rename" {% formaction %}="{{ url }}" class="btn btn-yellow">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> {{ label }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{% load i18n %}
|
|
||||||
<a href="#"
|
<a href="#"
|
||||||
hx-get="{{ url }}"
|
hx-get="{{ url }}"
|
||||||
hx-target="#htmx-modal-content"
|
hx-target="#htmx-modal-content"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
hx-select="form"
|
hx-select="form"
|
||||||
class="btn btn-red"
|
class="btn btn-red"
|
||||||
|
role="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#htmx-modal"
|
data-bs-target="#htmx-modal"
|
||||||
>
|
>
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {{ label }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{% load i18n %}
|
|
||||||
<a href="{{ url }}" class="btn btn-yellow" role="button">
|
<a href="{{ url }}" class="btn btn-yellow" role="button">
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit" %}
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> {{ label }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button type="button" class="btn btn-purple dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-purple dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<i class="mdi mdi-download"></i> {% trans "Export" %}
|
<i class="mdi mdi-download" aria-hidden="true"></i> {{ label }}
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a id="export_current_view" class="dropdown-item" href="?{% if url_params %}{{ url_params }}&{% endif %}export=table">{% trans "Current View" %}</a></li>
|
<li><a id="export_current_view" class="dropdown-item" href="?{% if url_params %}{{ url_params }}&{% endif %}export=table">{% trans "Current View" %}</a></li>
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
{% load i18n %}
|
<a href="{{ url }}" class="btn btn-cyan" role="button">
|
||||||
{% if url %}
|
<i class="mdi mdi-upload" aria-hidden="true"></i> {{ label }}
|
||||||
<a href="{{ url }}" type="button" class="btn btn-cyan">
|
</a>
|
||||||
<i class="mdi mdi-upload"></i> {% trans "Import" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{% load i18n %}
|
|
||||||
<form action="{{ url }}" method="post">
|
<form action="{{ url }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync" %}
|
<i class="mdi mdi-sync" aria-hidden="true"></i> {{ label }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ __all__ = (
|
|||||||
'content_type',
|
'content_type',
|
||||||
'content_type_id',
|
'content_type_id',
|
||||||
'fgcolor',
|
'fgcolor',
|
||||||
|
'getattr_',
|
||||||
'isodate',
|
'isodate',
|
||||||
'isodatetime',
|
'isodatetime',
|
||||||
'isotime',
|
'isotime',
|
||||||
@@ -88,6 +89,14 @@ def fgcolor(value, dark='000000', light='ffffff'):
|
|||||||
return f'#{foreground_color(value, dark, light)}'
|
return f'#{foreground_color(value, dark, light)}'
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter('getattr')
|
||||||
|
def getattr_(instance, name):
|
||||||
|
"""
|
||||||
|
Call getattr() on the object for the specified attribute.
|
||||||
|
"""
|
||||||
|
return getattr(instance, name, None)
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
def meta(model, attr):
|
def meta(model, attr):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.template import loader
|
||||||
from django.urls import NoReverseMatch, reverse
|
from django.urls import NoReverseMatch, reverse
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from extras.models import Bookmark, ExportTemplate, Subscription
|
from extras.models import Bookmark, ExportTemplate, Subscription
|
||||||
@@ -9,6 +12,7 @@ from utilities.querydict import prepare_cloned_fields
|
|||||||
from utilities.views import get_viewname
|
from utilities.views import get_viewname
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'action_buttons',
|
||||||
'add_button',
|
'add_button',
|
||||||
'bookmark_button',
|
'bookmark_button',
|
||||||
'bulk_delete_button',
|
'bulk_delete_button',
|
||||||
@@ -25,9 +29,14 @@ __all__ = (
|
|||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
#
|
@register.simple_tag(takes_context=True)
|
||||||
# Instance buttons
|
def action_buttons(context, actions, obj, multi=False):
|
||||||
#
|
buttons = [
|
||||||
|
loader.render_to_string(action.template_name, action.get_context(context, obj))
|
||||||
|
for action in actions if action.multi == multi
|
||||||
|
]
|
||||||
|
return mark_safe(''.join(buttons))
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('buttons/bookmark.html', takes_context=True)
|
@register.inclusion_tag('buttons/bookmark.html', takes_context=True)
|
||||||
def bookmark_button(context, instance):
|
def bookmark_button(context, instance):
|
||||||
@@ -60,42 +69,6 @@ def bookmark_button(context, instance):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('buttons/clone.html')
|
|
||||||
def clone_button(instance):
|
|
||||||
url = reverse(get_viewname(instance, 'add'))
|
|
||||||
|
|
||||||
# Populate cloned field values
|
|
||||||
param_string = prepare_cloned_fields(instance).urlencode()
|
|
||||||
if param_string:
|
|
||||||
url = f'{url}?{param_string}'
|
|
||||||
else:
|
|
||||||
url = None
|
|
||||||
|
|
||||||
return {
|
|
||||||
'url': url,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('buttons/edit.html')
|
|
||||||
def edit_button(instance):
|
|
||||||
viewname = get_viewname(instance, 'edit')
|
|
||||||
url = reverse(viewname, kwargs={'pk': instance.pk})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'url': url,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('buttons/delete.html')
|
|
||||||
def delete_button(instance):
|
|
||||||
viewname = get_viewname(instance, 'delete')
|
|
||||||
url = reverse(viewname, kwargs={'pk': instance.pk})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'url': url,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('buttons/subscribe.html', takes_context=True)
|
@register.inclusion_tag('buttons/subscribe.html', takes_context=True)
|
||||||
def subscribe_button(context, instance):
|
def subscribe_button(context, instance):
|
||||||
# Skip for objects which don't support notifications
|
# Skip for objects which don't support notifications
|
||||||
@@ -131,20 +104,70 @@ def subscribe_button(context, instance):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Legacy object buttons
|
||||||
|
#
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
|
@register.inclusion_tag('buttons/clone.html')
|
||||||
|
def clone_button(instance):
|
||||||
|
# Resolve URL path
|
||||||
|
viewname = get_viewname(instance, 'add')
|
||||||
|
try:
|
||||||
|
url = reverse(viewname)
|
||||||
|
except NoReverseMatch:
|
||||||
|
return {
|
||||||
|
'url': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Populate cloned field values and return full URL
|
||||||
|
param_string = prepare_cloned_fields(instance).urlencode()
|
||||||
|
return {
|
||||||
|
'url': f'{url}?{param_string}' if param_string else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
|
@register.inclusion_tag('buttons/edit.html')
|
||||||
|
def edit_button(instance):
|
||||||
|
viewname = get_viewname(instance, 'edit')
|
||||||
|
url = reverse(viewname, kwargs={'pk': instance.pk})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'url': url,
|
||||||
|
'label': _('Edit'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
|
@register.inclusion_tag('buttons/delete.html')
|
||||||
|
def delete_button(instance):
|
||||||
|
viewname = get_viewname(instance, 'delete')
|
||||||
|
url = reverse(viewname, kwargs={'pk': instance.pk})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'url': url,
|
||||||
|
'label': _('Delete'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
@register.inclusion_tag('buttons/sync.html')
|
@register.inclusion_tag('buttons/sync.html')
|
||||||
def sync_button(instance):
|
def sync_button(instance):
|
||||||
viewname = get_viewname(instance, 'sync')
|
viewname = get_viewname(instance, 'sync')
|
||||||
url = reverse(viewname, kwargs={'pk': instance.pk})
|
url = reverse(viewname, kwargs={'pk': instance.pk})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'label': _('Sync'),
|
||||||
'url': url,
|
'url': url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# List buttons
|
# Legacy list buttons
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
@register.inclusion_tag('buttons/add.html')
|
@register.inclusion_tag('buttons/add.html')
|
||||||
def add_button(model, action='add'):
|
def add_button(model, action='add'):
|
||||||
try:
|
try:
|
||||||
@@ -154,9 +177,11 @@ def add_button(model, action='add'):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'url': url,
|
'url': url,
|
||||||
|
'label': _('Add'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
@register.inclusion_tag('buttons/import.html')
|
@register.inclusion_tag('buttons/import.html')
|
||||||
def import_button(model, action='bulk_import'):
|
def import_button(model, action='bulk_import'):
|
||||||
try:
|
try:
|
||||||
@@ -166,9 +191,11 @@ def import_button(model, action='bulk_import'):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'url': url,
|
'url': url,
|
||||||
|
'label': _('Import'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
@register.inclusion_tag('buttons/export.html', takes_context=True)
|
@register.inclusion_tag('buttons/export.html', takes_context=True)
|
||||||
def export_button(context, model):
|
def export_button(context, model):
|
||||||
object_type = ObjectType.objects.get_for_model(model)
|
object_type = ObjectType.objects.get_for_model(model)
|
||||||
@@ -181,6 +208,7 @@ def export_button(context, model):
|
|||||||
export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type)
|
export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'label': _('Export'),
|
||||||
'perms': context['perms'],
|
'perms': context['perms'],
|
||||||
'object_type': object_type,
|
'object_type': object_type,
|
||||||
'url_params': context['request'].GET.urlencode() if context['request'].GET else '',
|
'url_params': context['request'].GET.urlencode() if context['request'].GET else '',
|
||||||
@@ -189,6 +217,7 @@ def export_button(context, model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
@register.inclusion_tag('buttons/bulk_edit.html', takes_context=True)
|
@register.inclusion_tag('buttons/bulk_edit.html', takes_context=True)
|
||||||
def bulk_edit_button(context, model, action='bulk_edit', query_params=None):
|
def bulk_edit_button(context, model, action='bulk_edit', query_params=None):
|
||||||
try:
|
try:
|
||||||
@@ -199,11 +228,13 @@ def bulk_edit_button(context, model, action='bulk_edit', query_params=None):
|
|||||||
url = None
|
url = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'htmx_navigation': context.get('htmx_navigation'),
|
'label': _('Edit Selected'),
|
||||||
'url': url,
|
'url': url,
|
||||||
|
'htmx_navigation': context.get('htmx_navigation'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove in NetBox v4.6
|
||||||
@register.inclusion_tag('buttons/bulk_delete.html', takes_context=True)
|
@register.inclusion_tag('buttons/bulk_delete.html', takes_context=True)
|
||||||
def bulk_delete_button(context, model, action='bulk_delete', query_params=None):
|
def bulk_delete_button(context, model, action='bulk_delete', query_params=None):
|
||||||
try:
|
try:
|
||||||
@@ -214,6 +245,7 @@ def bulk_delete_button(context, model, action='bulk_delete', query_params=None):
|
|||||||
url = None
|
url = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'htmx_navigation': context.get('htmx_navigation'),
|
'label': _('Delete Selected'),
|
||||||
'url': url,
|
'url': url,
|
||||||
|
'htmx_navigation': context.get('htmx_navigation'),
|
||||||
}
|
}
|
||||||
|
|||||||
26
netbox/virtualization/object_actions.py
Normal file
26
netbox/virtualization/object_actions.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from netbox.object_actions import ObjectAction
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BulkAddComponents',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BulkAddComponents(ObjectAction):
|
||||||
|
"""
|
||||||
|
Add components to the selected virtual machines.
|
||||||
|
"""
|
||||||
|
label = _('Add Components')
|
||||||
|
multi = True
|
||||||
|
permissions_required = {'change'}
|
||||||
|
template_name = 'virtualization/buttons/bulk_add_components.html'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_context(cls, context, obj):
|
||||||
|
return {
|
||||||
|
'perms': context.get('perms'),
|
||||||
|
'request': context.get('request'),
|
||||||
|
'formaction': context.get('formaction'),
|
||||||
|
'label': cls.label,
|
||||||
|
}
|
||||||
@@ -13,13 +13,16 @@ from dcim.tables import DeviceTable
|
|||||||
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||||
from ipam.models import IPAddress, VLANGroup
|
from ipam.models import IPAddress, VLANGroup
|
||||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.object_actions import (
|
||||||
|
AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename, DeleteObject, EditObject,
|
||||||
|
)
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.query_functions import CollateAsChar
|
from utilities.query_functions import CollateAsChar
|
||||||
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
|
from utilities.views import GetRelatedModelsMixin, ViewTab, register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .object_actions import BulkAddComponents
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -74,6 +77,11 @@ class ClusterTypeBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ClusterTypeBulkEditForm
|
form = forms.ClusterTypeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ClusterType, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ClusterTypeBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ClusterType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ClusterType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ClusterType, 'bulk_delete', path='delete', detail=False)
|
||||||
class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
|
class ClusterTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ClusterType.objects.annotate(
|
queryset = ClusterType.objects.annotate(
|
||||||
@@ -147,6 +155,11 @@ class ClusterGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ClusterGroupBulkEditForm
|
form = forms.ClusterGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ClusterGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ClusterGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = ClusterGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ClusterGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ClusterGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
|
class ClusterGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ClusterGroup.objects.annotate(
|
queryset = ClusterGroup.objects.annotate(
|
||||||
@@ -204,6 +217,7 @@ class ClusterVirtualMachinesView(generic.ObjectChildrenView):
|
|||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
filterset = filtersets.VirtualMachineFilterSet
|
filterset = filtersets.VirtualMachineFilterSet
|
||||||
filterset_form = forms.VirtualMachineFilterForm
|
filterset_form = forms.VirtualMachineFilterForm
|
||||||
|
actions = (EditObject, DeleteObject, BulkEdit)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Virtual Machines'),
|
label=_('Virtual Machines'),
|
||||||
badge=lambda obj: obj.virtual_machines.count(),
|
badge=lambda obj: obj.virtual_machines.count(),
|
||||||
@@ -222,14 +236,7 @@ class ClusterDevicesView(generic.ObjectChildrenView):
|
|||||||
table = DeviceTable
|
table = DeviceTable
|
||||||
filterset = DeviceFilterSet
|
filterset = DeviceFilterSet
|
||||||
filterset_form = DeviceFilterForm
|
filterset_form = DeviceFilterForm
|
||||||
template_name = 'virtualization/cluster/devices.html'
|
actions = (EditObject, DeleteObject, BulkEdit)
|
||||||
actions = {
|
|
||||||
'add': {'add'},
|
|
||||||
'export': {'view'},
|
|
||||||
'bulk_import': {'add'},
|
|
||||||
'bulk_edit': {'change'},
|
|
||||||
'bulk_remove_devices': {'change'},
|
|
||||||
}
|
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Devices'),
|
label=_('Devices'),
|
||||||
badge=lambda obj: obj.devices.count(),
|
badge=lambda obj: obj.devices.count(),
|
||||||
@@ -267,6 +274,11 @@ class ClusterBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ClusterBulkEditForm
|
form = forms.ClusterBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Cluster, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class ClusterBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Cluster.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Cluster, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Cluster, 'bulk_delete', path='delete', detail=False)
|
||||||
class ClusterBulkDeleteView(generic.BulkDeleteView):
|
class ClusterBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
@@ -317,50 +329,6 @@ class ClusterAddDevicesView(generic.ObjectEditView):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Cluster, 'remove_devices', path='devices/remove')
|
|
||||||
class ClusterRemoveDevicesView(generic.ObjectEditView):
|
|
||||||
queryset = Cluster.objects.all()
|
|
||||||
form = forms.ClusterRemoveDevicesForm
|
|
||||||
template_name = 'generic/bulk_remove.html'
|
|
||||||
|
|
||||||
def post(self, request, pk):
|
|
||||||
|
|
||||||
cluster = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
if '_confirm' in request.POST:
|
|
||||||
form = self.form(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
device_pks = form.cleaned_data['pk']
|
|
||||||
with transaction.atomic(using=router.db_for_write(Device)):
|
|
||||||
|
|
||||||
# Remove the selected Devices from the Cluster
|
|
||||||
for device in Device.objects.filter(pk__in=device_pks):
|
|
||||||
device.cluster = None
|
|
||||||
device.save()
|
|
||||||
|
|
||||||
messages.success(request, _("Removed {count} devices from cluster {cluster}").format(
|
|
||||||
count=len(device_pks),
|
|
||||||
cluster=cluster
|
|
||||||
))
|
|
||||||
return redirect(cluster.get_absolute_url())
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = self.form(initial={'pk': request.POST.getlist('pk')})
|
|
||||||
|
|
||||||
selected_objects = Device.objects.filter(pk__in=form.initial['pk'])
|
|
||||||
device_table = DeviceTable(list(selected_objects), orderable=False)
|
|
||||||
device_table.configure(request)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
|
||||||
'form': form,
|
|
||||||
'parent_obj': cluster,
|
|
||||||
'table': device_table,
|
|
||||||
'obj_type_plural': 'devices',
|
|
||||||
'return_url': cluster.get_absolute_url(),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Virtual machines
|
# Virtual machines
|
||||||
#
|
#
|
||||||
@@ -371,7 +339,7 @@ class VirtualMachineListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.VirtualMachineFilterSet
|
filterset = filtersets.VirtualMachineFilterSet
|
||||||
filterset_form = forms.VirtualMachineFilterForm
|
filterset_form = forms.VirtualMachineFilterForm
|
||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
template_name = 'virtualization/virtualmachine_list.html'
|
actions = (AddObject, BulkImport, BulkExport, BulkAddComponents, BulkEdit, BulkRename, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualMachine)
|
@register_model_view(VirtualMachine)
|
||||||
@@ -386,11 +354,7 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
|||||||
table = tables.VirtualMachineVMInterfaceTable
|
table = tables.VirtualMachineVMInterfaceTable
|
||||||
filterset = filtersets.VMInterfaceFilterSet
|
filterset = filtersets.VMInterfaceFilterSet
|
||||||
filterset_form = forms.VMInterfaceFilterForm
|
filterset_form = forms.VMInterfaceFilterForm
|
||||||
template_name = 'virtualization/virtualmachine/interfaces.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Interfaces'),
|
label=_('Interfaces'),
|
||||||
badge=lambda obj: obj.interface_count,
|
badge=lambda obj: obj.interface_count,
|
||||||
@@ -412,17 +376,13 @@ class VirtualMachineVirtualDisksView(generic.ObjectChildrenView):
|
|||||||
table = tables.VirtualMachineVirtualDiskTable
|
table = tables.VirtualMachineVirtualDiskTable
|
||||||
filterset = filtersets.VirtualDiskFilterSet
|
filterset = filtersets.VirtualDiskFilterSet
|
||||||
filterset_form = forms.VirtualDiskFilterForm
|
filterset_form = forms.VirtualDiskFilterForm
|
||||||
template_name = 'virtualization/virtualmachine/virtual_disks.html'
|
actions = (EditObject, DeleteObject, BulkEdit, BulkRename, BulkDelete)
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Virtual Disks'),
|
label=_('Virtual Disks'),
|
||||||
badge=lambda obj: obj.virtual_disk_count,
|
badge=lambda obj: obj.virtual_disk_count,
|
||||||
permission='virtualization.view_virtualdisk',
|
permission='virtualization.view_virtualdisk',
|
||||||
weight=500
|
weight=500
|
||||||
)
|
)
|
||||||
actions = {
|
|
||||||
**DEFAULT_ACTION_PERMISSIONS,
|
|
||||||
'bulk_rename': {'change'},
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
return parent.virtualdisks.restrict(request.user, 'view').prefetch_related('tags')
|
return parent.virtualdisks.restrict(request.user, 'view').prefetch_related('tags')
|
||||||
@@ -474,6 +434,11 @@ class VirtualMachineBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.VirtualMachineBulkEditForm
|
form = forms.VirtualMachineBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualMachine, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class VirtualMachineBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = VirtualMachine.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualMachine, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(VirtualMachine, 'bulk_delete', path='delete', detail=False)
|
||||||
class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
|
class VirtualMachineBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VirtualMachine.objects.prefetch_related('primary_ip4', 'primary_ip6')
|
queryset = VirtualMachine.objects.prefetch_related('primary_ip4', 'primary_ip6')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from ipam.tables import RouteTargetTable
|
from ipam.tables import RouteTargetTable
|
||||||
|
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.views import GetRelatedModelsMixin, register_model_view
|
from utilities.views import GetRelatedModelsMixin, register_model_view
|
||||||
@@ -58,6 +59,11 @@ class TunnelGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.TunnelGroupBulkEditForm
|
form = forms.TunnelGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(TunnelGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class TunnelGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = TunnelGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(TunnelGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(TunnelGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class TunnelGroupBulkDeleteView(generic.BulkDeleteView):
|
class TunnelGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = TunnelGroup.objects.annotate(
|
queryset = TunnelGroup.objects.annotate(
|
||||||
@@ -122,6 +128,11 @@ class TunnelBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.TunnelBulkEditForm
|
form = forms.TunnelBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Tunnel, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class TunnelBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = Tunnel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Tunnel, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(Tunnel, 'bulk_delete', path='delete', detail=False)
|
||||||
class TunnelBulkDeleteView(generic.BulkDeleteView):
|
class TunnelBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Tunnel.objects.annotate(
|
queryset = Tunnel.objects.annotate(
|
||||||
@@ -224,6 +235,11 @@ class IKEProposalBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.IKEProposalBulkEditForm
|
form = forms.IKEProposalBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IKEProposal, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class IKEProposalBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = IKEProposal.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IKEProposal, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(IKEProposal, 'bulk_delete', path='delete', detail=False)
|
||||||
class IKEProposalBulkDeleteView(generic.BulkDeleteView):
|
class IKEProposalBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IKEProposal.objects.all()
|
queryset = IKEProposal.objects.all()
|
||||||
@@ -274,6 +290,11 @@ class IKEPolicyBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.IKEPolicyBulkEditForm
|
form = forms.IKEPolicyBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IKEPolicy, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class IKEPolicyBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = IKEPolicy.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IKEPolicy, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(IKEPolicy, 'bulk_delete', path='delete', detail=False)
|
||||||
class IKEPolicyBulkDeleteView(generic.BulkDeleteView):
|
class IKEPolicyBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IKEPolicy.objects.all()
|
queryset = IKEPolicy.objects.all()
|
||||||
@@ -324,6 +345,11 @@ class IPSecProposalBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.IPSecProposalBulkEditForm
|
form = forms.IPSecProposalBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IPSecProposal, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class IPSecProposalBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = IPSecProposal.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IPSecProposal, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(IPSecProposal, 'bulk_delete', path='delete', detail=False)
|
||||||
class IPSecProposalBulkDeleteView(generic.BulkDeleteView):
|
class IPSecProposalBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IPSecProposal.objects.all()
|
queryset = IPSecProposal.objects.all()
|
||||||
@@ -374,6 +400,11 @@ class IPSecPolicyBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.IPSecPolicyBulkEditForm
|
form = forms.IPSecPolicyBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IPSecPolicy, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class IPSecPolicyBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = IPSecPolicy.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IPSecPolicy, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(IPSecPolicy, 'bulk_delete', path='delete', detail=False)
|
||||||
class IPSecPolicyBulkDeleteView(generic.BulkDeleteView):
|
class IPSecPolicyBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IPSecPolicy.objects.all()
|
queryset = IPSecPolicy.objects.all()
|
||||||
@@ -424,6 +455,11 @@ class IPSecProfileBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.IPSecProfileBulkEditForm
|
form = forms.IPSecProfileBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IPSecProfile, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class IPSecProfileBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = IPSecProfile.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(IPSecProfile, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(IPSecProfile, 'bulk_delete', path='delete', detail=False)
|
||||||
class IPSecProfileBulkDeleteView(generic.BulkDeleteView):
|
class IPSecProfileBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IPSecProfile.objects.all()
|
queryset = IPSecProfile.objects.all()
|
||||||
@@ -491,6 +527,11 @@ class L2VPNBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.L2VPNBulkEditForm
|
form = forms.L2VPNBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(L2VPN, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class L2VPNBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = L2VPN.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPN, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(L2VPN, 'bulk_delete', path='delete', detail=False)
|
||||||
class L2VPNBulkDeleteView(generic.BulkDeleteView):
|
class L2VPNBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = L2VPN.objects.all()
|
queryset = L2VPN.objects.all()
|
||||||
@@ -508,6 +549,7 @@ class L2VPNTerminationListView(generic.ObjectListView):
|
|||||||
table = tables.L2VPNTerminationTable
|
table = tables.L2VPNTerminationTable
|
||||||
filterset = filtersets.L2VPNTerminationFilterSet
|
filterset = filtersets.L2VPNTerminationFilterSet
|
||||||
filterset_form = forms.L2VPNTerminationFilterForm
|
filterset_form = forms.L2VPNTerminationFilterForm
|
||||||
|
actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(L2VPNTermination)
|
@register_model_view(L2VPNTermination)
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ class WirelessLANGroupBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.WirelessLANGroupBulkEditForm
|
form = forms.WirelessLANGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(WirelessLANGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class WirelessLANGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = WirelessLANGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(WirelessLANGroup, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(WirelessLANGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
class WirelessLANGroupBulkDeleteView(generic.BulkDeleteView):
|
class WirelessLANGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = WirelessLANGroup.objects.add_related_count(
|
queryset = WirelessLANGroup.objects.add_related_count(
|
||||||
@@ -137,6 +142,12 @@ class WirelessLANBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.WirelessLANBulkEditForm
|
form = forms.WirelessLANBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(WirelessLAN, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class WirelessLANBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = WirelessLAN.objects.all()
|
||||||
|
field_name = 'ssid'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(WirelessLAN, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(WirelessLAN, 'bulk_delete', path='delete', detail=False)
|
||||||
class WirelessLANBulkDeleteView(generic.BulkDeleteView):
|
class WirelessLANBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = WirelessLAN.objects.all()
|
queryset = WirelessLAN.objects.all()
|
||||||
@@ -187,6 +198,12 @@ class WirelessLinkBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.WirelessLinkBulkEditForm
|
form = forms.WirelessLinkBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(WirelessLink, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class WirelessLinkBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
field_name = 'ssid'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(WirelessLink, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(WirelessLink, 'bulk_delete', path='delete', detail=False)
|
||||||
class WirelessLinkBulkDeleteView(generic.BulkDeleteView):
|
class WirelessLinkBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = WirelessLink.objects.all()
|
queryset = WirelessLink.objects.all()
|
||||||
|
|||||||
Reference in New Issue
Block a user