mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-22 11:38:45 -06:00
Compare commits
3 Commits
fix-19669-
...
c2d3363930
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d3363930 | ||
|
|
6e30c11017 | ||
|
|
b01c75cf3a |
@@ -1,29 +1,28 @@
|
|||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_rq.queues import get_redis_connection
|
||||||
|
from django_rq.settings import QUEUES_LIST
|
||||||
|
from django_rq.utils import get_statistics
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from rq.job import Job as RQ_Job
|
||||||
|
from rq.worker import Worker
|
||||||
|
|
||||||
from core import filtersets
|
from core import filtersets
|
||||||
from core.choices import DataSourceStatusChoices
|
|
||||||
from core.jobs import SyncDataSourceJob
|
from core.jobs import SyncDataSourceJob
|
||||||
from core.models import *
|
from core.models import *
|
||||||
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
|
from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
|
||||||
from django_rq.queues import get_redis_connection
|
|
||||||
from django_rq.utils import get_statistics
|
|
||||||
from django_rq.settings import QUEUES_LIST
|
|
||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
from netbox.api.pagination import LimitOffsetListPagination
|
from netbox.api.pagination import LimitOffsetListPagination
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework.permissions import IsAdminUser
|
|
||||||
from rq.job import Job as RQ_Job
|
|
||||||
from rq.worker import Worker
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
@@ -50,10 +49,8 @@ class DataSourceViewSet(NetBoxModelViewSet):
|
|||||||
if not request.user.has_perm('core.sync_datasource', obj=datasource):
|
if not request.user.has_perm('core.sync_datasource', obj=datasource):
|
||||||
raise PermissionDenied(_("This user does not have permission to synchronize this data source."))
|
raise PermissionDenied(_("This user does not have permission to synchronize this data source."))
|
||||||
|
|
||||||
# Enqueue the sync job & update the DataSource's status
|
# Enqueue the sync job
|
||||||
SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
|
SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
|
||||||
datasource.status = DataSourceStatusChoices.QUEUED
|
|
||||||
DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
|
|
||||||
|
|
||||||
serializer = serializers.DataSourceSerializer(datasource, context={'request': request})
|
serializer = serializers.DataSourceSerializer(datasource, context={'request': request})
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ class SyncDataSourceJob(JobRunner):
|
|||||||
class Meta:
|
class Meta:
|
||||||
name = 'Synchronization'
|
name = 'Synchronization'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def enqueue(cls, *args, **kwargs):
|
||||||
|
job = super().enqueue(*args, **kwargs)
|
||||||
|
|
||||||
|
# Update the DataSource's synchronization status to queued
|
||||||
|
if datasource := job.object:
|
||||||
|
datasource.status = DataSourceStatusChoices.QUEUED
|
||||||
|
DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
|
||||||
|
|
||||||
|
return job
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from threading import local
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
|
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
|
||||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||||
from django.dispatch import receiver, Signal
|
from django.dispatch import receiver, Signal
|
||||||
|
from django.core.signals import request_finished
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||||
|
|
||||||
@@ -42,6 +44,10 @@ clear_events = Signal()
|
|||||||
# Change logging & event handling
|
# Change logging & event handling
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Used to track received signals per object
|
||||||
|
_signals_received = local()
|
||||||
|
|
||||||
|
|
||||||
@receiver((post_save, m2m_changed))
|
@receiver((post_save, m2m_changed))
|
||||||
def handle_changed_object(sender, instance, **kwargs):
|
def handle_changed_object(sender, instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -130,6 +136,16 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
if request is None:
|
if request is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check whether we've already processed a pre_delete signal for this object. (This can
|
||||||
|
# happen e.g. when both a parent object and its child are deleted simultaneously, due
|
||||||
|
# to cascading deletion.)
|
||||||
|
if not hasattr(_signals_received, 'pre_delete'):
|
||||||
|
_signals_received.pre_delete = set()
|
||||||
|
signature = (ContentType.objects.get_for_model(instance), instance.pk)
|
||||||
|
if signature in _signals_received.pre_delete:
|
||||||
|
return
|
||||||
|
_signals_received.pre_delete.add(signature)
|
||||||
|
|
||||||
# Record an ObjectChange if applicable
|
# Record an ObjectChange if applicable
|
||||||
if hasattr(instance, 'to_objectchange'):
|
if hasattr(instance, 'to_objectchange'):
|
||||||
if hasattr(instance, 'snapshot') and not getattr(instance, '_prechange_snapshot', None):
|
if hasattr(instance, 'snapshot') and not getattr(instance, '_prechange_snapshot', None):
|
||||||
@@ -179,6 +195,14 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
model_deletes.labels(instance._meta.model_name).inc()
|
model_deletes.labels(instance._meta.model_name).inc()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(request_finished)
|
||||||
|
def clear_signal_history(sender, **kwargs):
|
||||||
|
"""
|
||||||
|
Clear out the signals history once the request is finished.
|
||||||
|
"""
|
||||||
|
_signals_received.pre_delete = set()
|
||||||
|
|
||||||
|
|
||||||
@receiver(clear_events)
|
@receiver(clear_events)
|
||||||
def clear_events_queue(sender, **kwargs):
|
def clear_events_queue(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -346,6 +346,38 @@ class ChangeLogViewTest(ModelViewTestCase):
|
|||||||
self.assertEqual(changes[1].changed_object_type, ContentType.objects.get_for_model(Interface))
|
self.assertEqual(changes[1].changed_object_type, ContentType.objects.get_for_model(Interface))
|
||||||
self.assertEqual(changes[2].changed_object_type, ContentType.objects.get_for_model(Device))
|
self.assertEqual(changes[2].changed_object_type, ContentType.objects.get_for_model(Device))
|
||||||
|
|
||||||
|
def test_duplicate_deletions(self):
|
||||||
|
"""
|
||||||
|
Check that a cascading deletion event does not generate multiple "deleted" ObjectChange records for
|
||||||
|
the same object.
|
||||||
|
"""
|
||||||
|
role1 = DeviceRole(name='Role 1', slug='role-1')
|
||||||
|
role1.save()
|
||||||
|
role2 = DeviceRole(name='Role 2', slug='role-2', parent=role1)
|
||||||
|
role2.save()
|
||||||
|
pk_list = [role1.pk, role2.pk]
|
||||||
|
|
||||||
|
# Delete both objects simultaneously
|
||||||
|
form_data = {
|
||||||
|
'pk': pk_list,
|
||||||
|
'confirm': True,
|
||||||
|
'_confirm': True,
|
||||||
|
}
|
||||||
|
request = {
|
||||||
|
'path': reverse('dcim:devicerole_bulk_delete'),
|
||||||
|
'data': post_data(form_data),
|
||||||
|
}
|
||||||
|
self.add_permissions('dcim.delete_devicerole')
|
||||||
|
self.assertHttpStatus(self.client.post(**request), 302)
|
||||||
|
|
||||||
|
# This should result in exactly one change record per object
|
||||||
|
objectchanges = ObjectChange.objects.filter(
|
||||||
|
changed_object_type=ContentType.objects.get_for_model(DeviceRole),
|
||||||
|
changed_object_id__in=pk_list,
|
||||||
|
action=ObjectChangeActionChoices.ACTION_DELETE
|
||||||
|
)
|
||||||
|
self.assertEqual(objectchanges.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ChangeLogAPITest(APITestCase):
|
class ChangeLogAPITest(APITestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ 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, register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
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
|
||||||
@@ -78,12 +77,8 @@ class DataSourceSyncView(BaseObjectView):
|
|||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
datasource = get_object_or_404(self.queryset, pk=pk)
|
datasource = get_object_or_404(self.queryset, pk=pk)
|
||||||
|
# Enqueue the sync job
|
||||||
# Enqueue the sync job & update the DataSource's status
|
|
||||||
job = SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
|
job = SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
|
||||||
datasource.status = DataSourceStatusChoices.QUEUED
|
|
||||||
DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
|
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("Queued job #{id} to sync {datasource}").format(id=job.pk, datasource=datasource)
|
_("Queued job #{id} to sync {datasource}").format(id=job.pk, datasource=datasource)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views.static import serve
|
|
||||||
from django_rq.queues import get_connection
|
from django_rq.queues import get_connection
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -202,17 +200,6 @@ class ImageAttachmentViewSet(NetBoxModelViewSet):
|
|||||||
serializer_class = serializers.ImageAttachmentSerializer
|
serializer_class = serializers.ImageAttachmentSerializer
|
||||||
filterset_class = filtersets.ImageAttachmentFilterSet
|
filterset_class = filtersets.ImageAttachmentFilterSet
|
||||||
|
|
||||||
@action(
|
|
||||||
methods=['GET'],
|
|
||||||
detail=True,
|
|
||||||
url_path='download',
|
|
||||||
url_name='download',
|
|
||||||
)
|
|
||||||
def download(self, request, pk, *args, **kwargs):
|
|
||||||
obj = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
# Render and return the elevation as an SVG drawing with the correct content type
|
|
||||||
return serve(request, obj.image.name, document_root=settings.MEDIA_ROOT)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Journal entries
|
# Journal entries
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.files.base import File
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import make_aware, now
|
from django.utils.timezone import make_aware, now
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -618,38 +616,6 @@ class ImageAttachmentTest(
|
|||||||
)
|
)
|
||||||
ImageAttachment.objects.bulk_create(image_attachments)
|
ImageAttachment.objects.bulk_create(image_attachments)
|
||||||
|
|
||||||
def test_image_download(self):
|
|
||||||
self.add_permissions('extras.view_imageattachment')
|
|
||||||
ct = ContentType.objects.get_for_model(Site)
|
|
||||||
site = Site.objects.get(name='Site 1', slug='site-1')
|
|
||||||
|
|
||||||
image = Image.new('RGB', size=(1, 1), color=(255, 0, 0))
|
|
||||||
image.save('test_image_download.png', format='PNG')
|
|
||||||
image_file = File(open('test_image_download.png', 'rb'))
|
|
||||||
content = image_file.read()
|
|
||||||
|
|
||||||
attachment = ImageAttachment(
|
|
||||||
object_type=ct,
|
|
||||||
object_id=site.pk,
|
|
||||||
name='Image Attachment 4',
|
|
||||||
image_height=1,
|
|
||||||
image_width=1
|
|
||||||
)
|
|
||||||
attachment.image.save('test_image_download.png', image_file, save=True)
|
|
||||||
attachment.save()
|
|
||||||
|
|
||||||
image = ImageAttachment.objects.get(name='Image Attachment 4')
|
|
||||||
url = reverse('extras-api:imageattachment-download', kwargs={'pk': image.pk})
|
|
||||||
response = self.client.get(url, **self.header)
|
|
||||||
downloaded_content = b''.join(response.streaming_content)
|
|
||||||
|
|
||||||
self.assertEqual(response.headers.get('Content-Type'), 'image/png')
|
|
||||||
self.assertEqual(response.headers.get('Content-Length'), '69')
|
|
||||||
self.assertEqual(
|
|
||||||
response.headers.get('Content-Disposition'), f'inline; filename="site_{site.pk}_Image_Attachment_4.png"'
|
|
||||||
)
|
|
||||||
self.assertEqual(content, downloaded_content)
|
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryTest(APIViewTestCases.APIViewTestCase):
|
class JournalEntryTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-07-24 05:05+0000\n"
|
"POT-Creation-Date: 2025-07-25 05:07+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -215,8 +215,8 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_edit.py:344 netbox/dcim/forms/bulk_edit.py:730
|
#: netbox/dcim/forms/bulk_edit.py:344 netbox/dcim/forms/bulk_edit.py:730
|
||||||
#: netbox/dcim/forms/bulk_edit.py:935 netbox/dcim/forms/bulk_import.py:134
|
#: netbox/dcim/forms/bulk_edit.py:935 netbox/dcim/forms/bulk_import.py:134
|
||||||
#: netbox/dcim/forms/bulk_import.py:236 netbox/dcim/forms/bulk_import.py:337
|
#: netbox/dcim/forms/bulk_import.py:236 netbox/dcim/forms/bulk_import.py:337
|
||||||
#: netbox/dcim/forms/bulk_import.py:598 netbox/dcim/forms/bulk_import.py:1512
|
#: netbox/dcim/forms/bulk_import.py:598 netbox/dcim/forms/bulk_import.py:1539
|
||||||
#: netbox/dcim/forms/bulk_import.py:1540 netbox/dcim/forms/filtersets.py:89
|
#: netbox/dcim/forms/bulk_import.py:1567 netbox/dcim/forms/filtersets.py:89
|
||||||
#: netbox/dcim/forms/filtersets.py:227 netbox/dcim/forms/filtersets.py:344
|
#: netbox/dcim/forms/filtersets.py:227 netbox/dcim/forms/filtersets.py:344
|
||||||
#: netbox/dcim/forms/filtersets.py:441 netbox/dcim/forms/filtersets.py:773
|
#: netbox/dcim/forms/filtersets.py:441 netbox/dcim/forms/filtersets.py:773
|
||||||
#: netbox/dcim/forms/filtersets.py:992 netbox/dcim/forms/filtersets.py:1065
|
#: netbox/dcim/forms/filtersets.py:992 netbox/dcim/forms/filtersets.py:1065
|
||||||
@@ -656,13 +656,13 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/filtersets.py:321 netbox/dcim/forms/bulk_edit.py:216
|
#: netbox/circuits/forms/filtersets.py:321 netbox/dcim/forms/bulk_edit.py:216
|
||||||
#: netbox/dcim/forms/bulk_edit.py:656 netbox/dcim/forms/bulk_edit.py:866
|
#: netbox/dcim/forms/bulk_edit.py:656 netbox/dcim/forms/bulk_edit.py:866
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1235 netbox/dcim/forms/bulk_edit.py:1262
|
#: netbox/dcim/forms/bulk_edit.py:1235 netbox/dcim/forms/bulk_edit.py:1262
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1796 netbox/dcim/forms/filtersets.py:1132
|
#: netbox/dcim/forms/bulk_edit.py:1796 netbox/dcim/forms/bulk_import.py:1414
|
||||||
#: netbox/dcim/forms/filtersets.py:1390 netbox/dcim/forms/filtersets.py:1543
|
#: netbox/dcim/forms/filtersets.py:1132 netbox/dcim/forms/filtersets.py:1390
|
||||||
#: netbox/dcim/forms/filtersets.py:1567 netbox/dcim/tables/devices.py:748
|
#: netbox/dcim/forms/filtersets.py:1543 netbox/dcim/forms/filtersets.py:1567
|
||||||
#: netbox/dcim/tables/devices.py:804 netbox/dcim/tables/devices.py:1045
|
#: netbox/dcim/tables/devices.py:748 netbox/dcim/tables/devices.py:804
|
||||||
#: netbox/dcim/tables/devicetypes.py:256 netbox/dcim/tables/devicetypes.py:271
|
#: netbox/dcim/tables/devices.py:1045 netbox/dcim/tables/devicetypes.py:256
|
||||||
#: netbox/dcim/tables/racks.py:33 netbox/extras/forms/bulk_edit.py:303
|
#: netbox/dcim/tables/devicetypes.py:271 netbox/dcim/tables/racks.py:33
|
||||||
#: netbox/extras/tables/tables.py:487
|
#: netbox/extras/forms/bulk_edit.py:303 netbox/extras/tables/tables.py:487
|
||||||
#: netbox/templates/circuits/circuittype.html:30
|
#: netbox/templates/circuits/circuittype.html:30
|
||||||
#: netbox/templates/circuits/virtualcircuittype.html:30
|
#: netbox/templates/circuits/virtualcircuittype.html:30
|
||||||
#: netbox/templates/dcim/cable.html:40 netbox/templates/dcim/devicerole.html:38
|
#: netbox/templates/dcim/cable.html:40 netbox/templates/dcim/devicerole.html:38
|
||||||
@@ -694,7 +694,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_import.py:818 netbox/dcim/forms/bulk_import.py:838
|
#: netbox/dcim/forms/bulk_import.py:818 netbox/dcim/forms/bulk_import.py:838
|
||||||
#: netbox/dcim/forms/bulk_import.py:924 netbox/dcim/forms/bulk_import.py:1018
|
#: netbox/dcim/forms/bulk_import.py:924 netbox/dcim/forms/bulk_import.py:1018
|
||||||
#: netbox/dcim/forms/bulk_import.py:1060 netbox/dcim/forms/bulk_import.py:1395
|
#: netbox/dcim/forms/bulk_import.py:1060 netbox/dcim/forms/bulk_import.py:1395
|
||||||
#: netbox/dcim/forms/bulk_import.py:1577 netbox/dcim/forms/filtersets.py:1023
|
#: netbox/dcim/forms/bulk_import.py:1604 netbox/dcim/forms/filtersets.py:1023
|
||||||
#: netbox/dcim/forms/filtersets.py:1122 netbox/dcim/forms/filtersets.py:1243
|
#: netbox/dcim/forms/filtersets.py:1122 netbox/dcim/forms/filtersets.py:1243
|
||||||
#: netbox/dcim/forms/filtersets.py:1315 netbox/dcim/forms/filtersets.py:1340
|
#: netbox/dcim/forms/filtersets.py:1315 netbox/dcim/forms/filtersets.py:1340
|
||||||
#: netbox/dcim/forms/filtersets.py:1364 netbox/dcim/forms/filtersets.py:1384
|
#: netbox/dcim/forms/filtersets.py:1364 netbox/dcim/forms/filtersets.py:1384
|
||||||
@@ -765,7 +765,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_import.py:150 netbox/dcim/forms/bulk_import.py:254
|
#: netbox/dcim/forms/bulk_import.py:150 netbox/dcim/forms/bulk_import.py:254
|
||||||
#: netbox/dcim/forms/bulk_import.py:563 netbox/dcim/forms/bulk_import.py:717
|
#: netbox/dcim/forms/bulk_import.py:563 netbox/dcim/forms/bulk_import.py:717
|
||||||
#: netbox/dcim/forms/bulk_import.py:1168 netbox/dcim/forms/bulk_import.py:1389
|
#: netbox/dcim/forms/bulk_import.py:1168 netbox/dcim/forms/bulk_import.py:1389
|
||||||
#: netbox/dcim/forms/bulk_import.py:1572 netbox/dcim/forms/bulk_import.py:1636
|
#: netbox/dcim/forms/bulk_import.py:1599 netbox/dcim/forms/bulk_import.py:1663
|
||||||
#: netbox/dcim/forms/filtersets.py:180 netbox/dcim/forms/filtersets.py:239
|
#: netbox/dcim/forms/filtersets.py:180 netbox/dcim/forms/filtersets.py:239
|
||||||
#: netbox/dcim/forms/filtersets.py:361 netbox/dcim/forms/filtersets.py:819
|
#: netbox/dcim/forms/filtersets.py:361 netbox/dcim/forms/filtersets.py:819
|
||||||
#: netbox/dcim/forms/filtersets.py:944 netbox/dcim/forms/filtersets.py:1026
|
#: netbox/dcim/forms/filtersets.py:944 netbox/dcim/forms/filtersets.py:1026
|
||||||
@@ -843,7 +843,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_import.py:110 netbox/dcim/forms/bulk_import.py:155
|
#: netbox/dcim/forms/bulk_import.py:110 netbox/dcim/forms/bulk_import.py:155
|
||||||
#: netbox/dcim/forms/bulk_import.py:247 netbox/dcim/forms/bulk_import.py:362
|
#: netbox/dcim/forms/bulk_import.py:247 netbox/dcim/forms/bulk_import.py:362
|
||||||
#: netbox/dcim/forms/bulk_import.py:537 netbox/dcim/forms/bulk_import.py:1401
|
#: netbox/dcim/forms/bulk_import.py:537 netbox/dcim/forms/bulk_import.py:1401
|
||||||
#: netbox/dcim/forms/bulk_import.py:1629 netbox/dcim/forms/filtersets.py:175
|
#: netbox/dcim/forms/bulk_import.py:1656 netbox/dcim/forms/filtersets.py:175
|
||||||
#: netbox/dcim/forms/filtersets.py:207 netbox/dcim/forms/filtersets.py:325
|
#: netbox/dcim/forms/filtersets.py:207 netbox/dcim/forms/filtersets.py:325
|
||||||
#: netbox/dcim/forms/filtersets.py:401 netbox/dcim/forms/filtersets.py:422
|
#: netbox/dcim/forms/filtersets.py:401 netbox/dcim/forms/filtersets.py:422
|
||||||
#: netbox/dcim/forms/filtersets.py:742 netbox/dcim/forms/filtersets.py:936
|
#: netbox/dcim/forms/filtersets.py:742 netbox/dcim/forms/filtersets.py:936
|
||||||
@@ -1149,7 +1149,7 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/bulk_import.py:229 netbox/dcim/forms/bulk_import.py:93
|
#: netbox/circuits/forms/bulk_import.py:229 netbox/dcim/forms/bulk_import.py:93
|
||||||
#: netbox/dcim/forms/bulk_import.py:152 netbox/dcim/forms/bulk_import.py:256
|
#: netbox/dcim/forms/bulk_import.py:152 netbox/dcim/forms/bulk_import.py:256
|
||||||
#: netbox/dcim/forms/bulk_import.py:565 netbox/dcim/forms/bulk_import.py:719
|
#: netbox/dcim/forms/bulk_import.py:565 netbox/dcim/forms/bulk_import.py:719
|
||||||
#: netbox/dcim/forms/bulk_import.py:1170 netbox/dcim/forms/bulk_import.py:1574
|
#: netbox/dcim/forms/bulk_import.py:1170 netbox/dcim/forms/bulk_import.py:1601
|
||||||
#: netbox/ipam/forms/bulk_import.py:197 netbox/ipam/forms/bulk_import.py:265
|
#: netbox/ipam/forms/bulk_import.py:197 netbox/ipam/forms/bulk_import.py:265
|
||||||
#: netbox/ipam/forms/bulk_import.py:301 netbox/ipam/forms/bulk_import.py:498
|
#: netbox/ipam/forms/bulk_import.py:301 netbox/ipam/forms/bulk_import.py:498
|
||||||
#: netbox/ipam/forms/bulk_import.py:511
|
#: netbox/ipam/forms/bulk_import.py:511
|
||||||
@@ -1165,8 +1165,8 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/bulk_import.py:236
|
#: netbox/circuits/forms/bulk_import.py:236
|
||||||
#: netbox/dcim/forms/bulk_import.py:114 netbox/dcim/forms/bulk_import.py:159
|
#: netbox/dcim/forms/bulk_import.py:114 netbox/dcim/forms/bulk_import.py:159
|
||||||
#: netbox/dcim/forms/bulk_import.py:366 netbox/dcim/forms/bulk_import.py:541
|
#: netbox/dcim/forms/bulk_import.py:366 netbox/dcim/forms/bulk_import.py:541
|
||||||
#: netbox/dcim/forms/bulk_import.py:1405 netbox/dcim/forms/bulk_import.py:1569
|
#: netbox/dcim/forms/bulk_import.py:1405 netbox/dcim/forms/bulk_import.py:1596
|
||||||
#: netbox/dcim/forms/bulk_import.py:1633 netbox/ipam/forms/bulk_import.py:45
|
#: netbox/dcim/forms/bulk_import.py:1660 netbox/ipam/forms/bulk_import.py:45
|
||||||
#: netbox/ipam/forms/bulk_import.py:74 netbox/ipam/forms/bulk_import.py:102
|
#: netbox/ipam/forms/bulk_import.py:74 netbox/ipam/forms/bulk_import.py:102
|
||||||
#: netbox/ipam/forms/bulk_import.py:122 netbox/ipam/forms/bulk_import.py:142
|
#: netbox/ipam/forms/bulk_import.py:122 netbox/ipam/forms/bulk_import.py:142
|
||||||
#: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:260
|
#: netbox/ipam/forms/bulk_import.py:171 netbox/ipam/forms/bulk_import.py:260
|
||||||
@@ -1246,8 +1246,8 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_edit.py:466 netbox/dcim/forms/bulk_edit.py:735
|
#: netbox/dcim/forms/bulk_edit.py:466 netbox/dcim/forms/bulk_edit.py:735
|
||||||
#: netbox/dcim/forms/bulk_edit.py:790 netbox/dcim/forms/bulk_edit.py:944
|
#: netbox/dcim/forms/bulk_edit.py:790 netbox/dcim/forms/bulk_edit.py:944
|
||||||
#: netbox/dcim/forms/bulk_import.py:241 netbox/dcim/forms/bulk_import.py:343
|
#: netbox/dcim/forms/bulk_import.py:241 netbox/dcim/forms/bulk_import.py:343
|
||||||
#: netbox/dcim/forms/bulk_import.py:604 netbox/dcim/forms/bulk_import.py:1518
|
#: netbox/dcim/forms/bulk_import.py:604 netbox/dcim/forms/bulk_import.py:1545
|
||||||
#: netbox/dcim/forms/bulk_import.py:1552 netbox/dcim/forms/filtersets.py:97
|
#: netbox/dcim/forms/bulk_import.py:1579 netbox/dcim/forms/filtersets.py:97
|
||||||
#: netbox/dcim/forms/filtersets.py:324 netbox/dcim/forms/filtersets.py:358
|
#: netbox/dcim/forms/filtersets.py:324 netbox/dcim/forms/filtersets.py:358
|
||||||
#: netbox/dcim/forms/filtersets.py:398 netbox/dcim/forms/filtersets.py:449
|
#: netbox/dcim/forms/filtersets.py:398 netbox/dcim/forms/filtersets.py:449
|
||||||
#: netbox/dcim/forms/filtersets.py:739 netbox/dcim/forms/filtersets.py:782
|
#: netbox/dcim/forms/filtersets.py:739 netbox/dcim/forms/filtersets.py:782
|
||||||
@@ -1949,7 +1949,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_import.py:1007 netbox/dcim/forms/bulk_import.py:1055
|
#: netbox/dcim/forms/bulk_import.py:1007 netbox/dcim/forms/bulk_import.py:1055
|
||||||
#: netbox/dcim/forms/bulk_import.py:1072 netbox/dcim/forms/bulk_import.py:1084
|
#: netbox/dcim/forms/bulk_import.py:1072 netbox/dcim/forms/bulk_import.py:1084
|
||||||
#: netbox/dcim/forms/bulk_import.py:1132 netbox/dcim/forms/bulk_import.py:1254
|
#: netbox/dcim/forms/bulk_import.py:1132 netbox/dcim/forms/bulk_import.py:1254
|
||||||
#: netbox/dcim/forms/bulk_import.py:1623 netbox/dcim/forms/connections.py:24
|
#: netbox/dcim/forms/bulk_import.py:1650 netbox/dcim/forms/connections.py:24
|
||||||
#: netbox/dcim/forms/filtersets.py:133 netbox/dcim/forms/filtersets.py:941
|
#: netbox/dcim/forms/filtersets.py:133 netbox/dcim/forms/filtersets.py:941
|
||||||
#: netbox/dcim/forms/filtersets.py:973 netbox/dcim/forms/filtersets.py:1119
|
#: netbox/dcim/forms/filtersets.py:973 netbox/dcim/forms/filtersets.py:1119
|
||||||
#: netbox/dcim/forms/filtersets.py:1310 netbox/dcim/forms/filtersets.py:1335
|
#: netbox/dcim/forms/filtersets.py:1310 netbox/dcim/forms/filtersets.py:1335
|
||||||
@@ -4194,8 +4194,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:465 netbox/dcim/forms/bulk_edit.py:972
|
#: netbox/dcim/forms/bulk_edit.py:465 netbox/dcim/forms/bulk_edit.py:972
|
||||||
#: netbox/dcim/forms/bulk_import.py:350 netbox/dcim/forms/bulk_import.py:353
|
#: netbox/dcim/forms/bulk_import.py:350 netbox/dcim/forms/bulk_import.py:353
|
||||||
#: netbox/dcim/forms/bulk_import.py:611 netbox/dcim/forms/bulk_import.py:1559
|
#: netbox/dcim/forms/bulk_import.py:611 netbox/dcim/forms/bulk_import.py:1586
|
||||||
#: netbox/dcim/forms/bulk_import.py:1563 netbox/dcim/forms/filtersets.py:106
|
#: netbox/dcim/forms/bulk_import.py:1590 netbox/dcim/forms/filtersets.py:106
|
||||||
#: netbox/dcim/forms/filtersets.py:326 netbox/dcim/forms/filtersets.py:407
|
#: netbox/dcim/forms/filtersets.py:326 netbox/dcim/forms/filtersets.py:407
|
||||||
#: netbox/dcim/forms/filtersets.py:421 netbox/dcim/forms/filtersets.py:459
|
#: netbox/dcim/forms/filtersets.py:421 netbox/dcim/forms/filtersets.py:459
|
||||||
#: netbox/dcim/forms/filtersets.py:792 netbox/dcim/forms/filtersets.py:1005
|
#: netbox/dcim/forms/filtersets.py:792 netbox/dcim/forms/filtersets.py:1005
|
||||||
@@ -4405,17 +4405,17 @@ msgstr ""
|
|||||||
msgid "Domain"
|
msgid "Domain"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:967 netbox/dcim/forms/bulk_import.py:1546
|
#: netbox/dcim/forms/bulk_edit.py:967 netbox/dcim/forms/bulk_import.py:1573
|
||||||
#: netbox/dcim/forms/filtersets.py:1226 netbox/dcim/forms/model_forms.py:855
|
#: netbox/dcim/forms/filtersets.py:1226 netbox/dcim/forms/model_forms.py:855
|
||||||
msgid "Power panel"
|
msgid "Power panel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:989 netbox/dcim/forms/bulk_import.py:1582
|
#: netbox/dcim/forms/bulk_edit.py:989 netbox/dcim/forms/bulk_import.py:1609
|
||||||
#: netbox/dcim/forms/filtersets.py:1248 netbox/templates/dcim/powerfeed.html:83
|
#: netbox/dcim/forms/filtersets.py:1248 netbox/templates/dcim/powerfeed.html:83
|
||||||
msgid "Supply"
|
msgid "Supply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:995 netbox/dcim/forms/bulk_import.py:1587
|
#: netbox/dcim/forms/bulk_edit.py:995 netbox/dcim/forms/bulk_import.py:1614
|
||||||
#: netbox/dcim/forms/filtersets.py:1253 netbox/templates/dcim/powerfeed.html:95
|
#: netbox/dcim/forms/filtersets.py:1253 netbox/templates/dcim/powerfeed.html:95
|
||||||
msgid "Phase"
|
msgid "Phase"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4653,7 +4653,7 @@ msgid "available options"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:137 netbox/dcim/forms/bulk_import.py:601
|
#: netbox/dcim/forms/bulk_import.py:137 netbox/dcim/forms/bulk_import.py:601
|
||||||
#: netbox/dcim/forms/bulk_import.py:1543 netbox/ipam/forms/bulk_import.py:479
|
#: netbox/dcim/forms/bulk_import.py:1570 netbox/ipam/forms/bulk_import.py:479
|
||||||
#: netbox/virtualization/forms/bulk_import.py:64
|
#: netbox/virtualization/forms/bulk_import.py:64
|
||||||
#: netbox/virtualization/forms/bulk_import.py:95
|
#: netbox/virtualization/forms/bulk_import.py:95
|
||||||
msgid "Assigned site"
|
msgid "Assigned site"
|
||||||
@@ -4716,7 +4716,7 @@ msgstr ""
|
|||||||
msgid "Parent site"
|
msgid "Parent site"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:347 netbox/dcim/forms/bulk_import.py:1556
|
#: netbox/dcim/forms/bulk_import.py:347 netbox/dcim/forms/bulk_import.py:1583
|
||||||
msgid "Rack's location (if any)"
|
msgid "Rack's location (if any)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4767,7 +4767,7 @@ msgstr ""
|
|||||||
msgid "Limit platform assignments to this manufacturer"
|
msgid "Limit platform assignments to this manufacturer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:534 netbox/dcim/forms/bulk_import.py:1626
|
#: netbox/dcim/forms/bulk_import.py:534 netbox/dcim/forms/bulk_import.py:1653
|
||||||
#: netbox/tenancy/forms/bulk_import.py:105
|
#: netbox/tenancy/forms/bulk_import.py:105
|
||||||
msgid "Assigned role"
|
msgid "Assigned role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5100,66 +5100,77 @@ msgstr ""
|
|||||||
msgid "Connection status"
|
msgid "Connection status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1463
|
#: netbox/dcim/forms/bulk_import.py:1417
|
||||||
#, python-brace-format
|
msgid "Color name (e.g. \"Red\") or hex code (e.g. \"f44336\")"
|
||||||
msgid "Side {side_upper}: {device} {termination_object} is already connected"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1469
|
#: netbox/dcim/forms/bulk_import.py:1469
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
msgid "Side {side_upper}: {device} {termination_object} is already connected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/dcim/forms/bulk_import.py:1475
|
||||||
|
#, python-brace-format
|
||||||
msgid "{side_upper} side termination not found: {device} {name}"
|
msgid "{side_upper} side termination not found: {device} {name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1494 netbox/dcim/forms/model_forms.py:891
|
#: netbox/dcim/forms/bulk_import.py:1496
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"{color} did not match any used color name and was longer than six "
|
||||||
|
"characters: invalid hex."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/dcim/forms/bulk_import.py:1521 netbox/dcim/forms/model_forms.py:891
|
||||||
#: netbox/dcim/tables/devices.py:1069 netbox/templates/dcim/device.html:138
|
#: netbox/dcim/tables/devices.py:1069 netbox/templates/dcim/device.html:138
|
||||||
#: netbox/templates/dcim/virtualchassis.html:27
|
#: netbox/templates/dcim/virtualchassis.html:27
|
||||||
#: netbox/templates/dcim/virtualchassis.html:67
|
#: netbox/templates/dcim/virtualchassis.html:67
|
||||||
msgid "Master"
|
msgid "Master"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1498
|
#: netbox/dcim/forms/bulk_import.py:1525
|
||||||
msgid "Master device"
|
msgid "Master device"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1515
|
#: netbox/dcim/forms/bulk_import.py:1542
|
||||||
msgid "Name of parent site"
|
msgid "Name of parent site"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1549
|
#: netbox/dcim/forms/bulk_import.py:1576
|
||||||
msgid "Upstream power panel"
|
msgid "Upstream power panel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1579
|
#: netbox/dcim/forms/bulk_import.py:1606
|
||||||
msgid "Primary or redundant"
|
msgid "Primary or redundant"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1584
|
#: netbox/dcim/forms/bulk_import.py:1611
|
||||||
msgid "Supply type (AC/DC)"
|
msgid "Supply type (AC/DC)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1589
|
#: netbox/dcim/forms/bulk_import.py:1616
|
||||||
msgid "Single or three-phase"
|
msgid "Single or three-phase"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1640 netbox/dcim/forms/model_forms.py:1847
|
#: netbox/dcim/forms/bulk_import.py:1667 netbox/dcim/forms/model_forms.py:1847
|
||||||
#: netbox/templates/dcim/device.html:196
|
#: netbox/templates/dcim/device.html:196
|
||||||
#: netbox/templates/dcim/virtualdevicecontext.html:30
|
#: netbox/templates/dcim/virtualdevicecontext.html:30
|
||||||
#: netbox/templates/virtualization/virtualmachine.html:52
|
#: netbox/templates/virtualization/virtualmachine.html:52
|
||||||
msgid "Primary IPv4"
|
msgid "Primary IPv4"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1644
|
#: netbox/dcim/forms/bulk_import.py:1671
|
||||||
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
|
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1647 netbox/dcim/forms/model_forms.py:1856
|
#: netbox/dcim/forms/bulk_import.py:1674 netbox/dcim/forms/model_forms.py:1856
|
||||||
#: netbox/templates/dcim/device.html:212
|
#: netbox/templates/dcim/device.html:212
|
||||||
#: netbox/templates/dcim/virtualdevicecontext.html:41
|
#: netbox/templates/dcim/virtualdevicecontext.html:41
|
||||||
#: netbox/templates/virtualization/virtualmachine.html:68
|
#: netbox/templates/virtualization/virtualmachine.html:68
|
||||||
msgid "Primary IPv6"
|
msgid "Primary IPv6"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1651
|
#: netbox/dcim/forms/bulk_import.py:1678
|
||||||
msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64"
|
msgid "IPv6 address with prefix length, e.g. 2001:db8::1/64"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user