فهرست منبع

Merge branch 'develop' into feature

Arthur Hanson 1 سال پیش
والد
کامیت
99904c1518
39فایلهای تغییر یافته به همراه22427 افزوده شده و 27178 حذف شده
  1. 2 3
      .github/ISSUE_TEMPLATE/01-feature_request.yaml
  2. 2 3
      .github/ISSUE_TEMPLATE/02-bug_report.yaml
  3. 1 1
      README.md
  4. 5 1
      base_requirements.txt
  5. 13 1
      docs/release-notes/version-4.1.md
  6. 4 2
      netbox/core/models/jobs.py
  7. 1 1
      netbox/dcim/models/cables.py
  8. 2 0
      netbox/ipam/fields.py
  9. 7 7
      netbox/ipam/models/ip.py
  10. 29 0
      netbox/ipam/tests/test_models.py
  11. 1 1
      netbox/manage.py
  12. 8 8
      netbox/netbox/tests/test_jobs.py
  13. 1 1
      netbox/project-static/package.json
  14. 4 4
      netbox/project-static/yarn.lock
  15. 2 2
      netbox/release.yaml
  16. 1 14
      netbox/templates/dcim/devicetype/component_templates.html
  17. 2 0
      netbox/templates/dcim/inc/devicetype_breadcrumbs.html
  18. 38 0
      netbox/templates/dcim/inc/moduletype_buttons.html
  19. 12 1
      netbox/templates/dcim/moduletype.html
  20. 0 48
      netbox/templates/dcim/moduletype/base.html
  21. 33 40
      netbox/templates/dcim/moduletype/component_templates.html
  22. 1540 1871
      netbox/translations/cs/LC_MESSAGES/django.po
  23. 1118 1387
      netbox/translations/da/LC_MESSAGES/django.po
  24. 1118 1387
      netbox/translations/de/LC_MESSAGES/django.po
  25. 3034 3555
      netbox/translations/en/LC_MESSAGES/django.po
  26. 1118 1387
      netbox/translations/es/LC_MESSAGES/django.po
  27. 1118 1387
      netbox/translations/fr/LC_MESSAGES/django.po
  28. 1118 1387
      netbox/translations/it/LC_MESSAGES/django.po
  29. 2065 2465
      netbox/translations/ja/LC_MESSAGES/django.po
  30. 1118 1387
      netbox/translations/nl/LC_MESSAGES/django.po
  31. BIN
      netbox/translations/pl/LC_MESSAGES/django.mo
  32. 1546 1877
      netbox/translations/pl/LC_MESSAGES/django.po
  33. 1118 1387
      netbox/translations/pt/LC_MESSAGES/django.po
  34. 1118 1387
      netbox/translations/ru/LC_MESSAGES/django.po
  35. 1118 1387
      netbox/translations/tr/LC_MESSAGES/django.po
  36. BIN
      netbox/translations/uk/LC_MESSAGES/django.mo
  37. 1541 1872
      netbox/translations/uk/LC_MESSAGES/django.po
  38. 2464 2911
      netbox/translations/zh/LC_MESSAGES/django.po
  39. 7 6
      requirements.txt

+ 2 - 3
.github/ISSUE_TEMPLATE/01-feature_request.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
     attributes:
       label: NetBox version
       label: NetBox version
       description: What version of NetBox are you currently running?
       description: What version of NetBox are you currently running?
-      placeholder: v4.1.4
+      placeholder: v4.1.5
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown
@@ -36,9 +36,8 @@ body:
       options:
       options:
         - I volunteer to perform this work (if approved)
         - I volunteer to perform this work (if approved)
         - I'm a NetBox Labs customer
         - I'm a NetBox Labs customer
-        - This is a very minor change
         - N/A
         - N/A
-      default: 3
+      default: 2
     validations:
     validations:
       required: true
       required: true
   - type: textarea
   - type: textarea

+ 2 - 3
.github/ISSUE_TEMPLATE/02-bug_report.yaml

@@ -31,16 +31,15 @@ body:
       options:
       options:
         - I volunteer to perform this work (if approved)
         - I volunteer to perform this work (if approved)
         - I'm a NetBox Labs customer
         - I'm a NetBox Labs customer
-        - This is preventing me from using NetBox
         - N/A
         - N/A
-      default: 3
+      default: 2
     validations:
     validations:
       required: true
       required: true
   - type: input
   - type: input
     attributes:
     attributes:
       label: NetBox Version
       label: NetBox Version
       description: What version of NetBox are you currently running?
       description: What version of NetBox are you currently running?
-      placeholder: v4.1.4
+      placeholder: v4.1.5
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

+ 1 - 1
README.md

@@ -1,5 +1,5 @@
 <div align="center">
 <div align="center">
-  <img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
+  <img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo_light.svg" width="400" alt="NetBox logo" />
   <p><strong>The cornerstone of every automated network</strong></p>
   <p><strong>The cornerstone of every automated network</strong></p>
   <a href="https://github.com/netbox-community/netbox/releases"><img src="https://img.shields.io/github/v/release/netbox-community/netbox" alt="Latest release" /></a>
   <a href="https://github.com/netbox-community/netbox/releases"><img src="https://img.shields.io/github/v/release/netbox-community/netbox" alt="Latest release" /></a>
   <a href="https://github.com/netbox-community/netbox/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License" /></a>
   <a href="https://github.com/netbox-community/netbox/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License" /></a>

+ 5 - 1
base_requirements.txt

@@ -42,7 +42,7 @@ django-rich
 
 
 # Django integration for RQ (Reqis queuing)
 # Django integration for RQ (Reqis queuing)
 # https://github.com/rq/django-rq/blob/master/CHANGELOG.md
 # https://github.com/rq/django-rq/blob/master/CHANGELOG.md
-django-rq
+django-rq<3.0
 
 
 # Abstraction models for rendering and paginating HTML tables
 # Abstraction models for rendering and paginating HTML tables
 # https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md
 # https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md
@@ -116,6 +116,10 @@ PyYAML
 # https://github.com/psf/requests/blob/main/HISTORY.md
 # https://github.com/psf/requests/blob/main/HISTORY.md
 requests
 requests
 
 
+# rq
+# https://github.com/rq/rq/blob/master/CHANGES.md
+rq<2.0
+
 # Social authentication framework
 # Social authentication framework
 # https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md
 # https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md
 social-auth-core
 social-auth-core

+ 13 - 1
docs/release-notes/version-4.1.md

@@ -1,14 +1,26 @@
 # NetBox v4.1
 # NetBox v4.1
 
 
-## v4.1.5 (FUTURE)
+## v4.1.5 (2024-10-28)
+
+### Enhancements
+
+* [#17789](https://github.com/netbox-community/netbox/issues/17789) - Provide a single "scope" field for bulk editing VLAN group scope assignments
 
 
 ### Bug Fixes
 ### Bug Fixes
 
 
+* [#17358](https://github.com/netbox-community/netbox/issues/17358) - Fix validation of overlapping IP ranges
+* [#17374](https://github.com/netbox-community/netbox/issues/17374) - Fix styling of highlighted table rows in dark mode
+* [#17460](https://github.com/netbox-community/netbox/issues/17460) - Ensure bulk action buttons are consistent for device type components
+* [#17635](https://github.com/netbox-community/netbox/issues/17635) - Ensure AbortTransaction is caught when running a custom script with `commit=False`
+* [#17685](https://github.com/netbox-community/netbox/issues/17685) - Ensure background jobs are validated before being scheduled
 * [#17710](https://github.com/netbox-community/netbox/issues/17710) - Remove cached fields on CableTermination model from GraphQL API
 * [#17710](https://github.com/netbox-community/netbox/issues/17710) - Remove cached fields on CableTermination model from GraphQL API
 * [#17740](https://github.com/netbox-community/netbox/issues/17740) - Ensure support for image attachments with a `.webp` file extension
 * [#17740](https://github.com/netbox-community/netbox/issues/17740) - Ensure support for image attachments with a `.webp` file extension
 * [#17749](https://github.com/netbox-community/netbox/issues/17749) - Restore missing `devicetypes` and `children` fields for several objects in GraphQL API
 * [#17749](https://github.com/netbox-community/netbox/issues/17749) - Restore missing `devicetypes` and `children` fields for several objects in GraphQL API
 * [#17754](https://github.com/netbox-community/netbox/issues/17754) - Remove paginator from version history table under plugin view
 * [#17754](https://github.com/netbox-community/netbox/issues/17754) - Remove paginator from version history table under plugin view
 * [#17759](https://github.com/netbox-community/netbox/issues/17759) - Retain `job_timeout` value when scheduling a recurring custom script
 * [#17759](https://github.com/netbox-community/netbox/issues/17759) - Retain `job_timeout` value when scheduling a recurring custom script
+* [#17774](https://github.com/netbox-community/netbox/issues/17774) - Fix SSO login support for Entra ID (formerly Azure AD)
+* [#17802](https://github.com/netbox-community/netbox/issues/17802) - Fix background color for bulk rename buttons in list views
+* [#17838](https://github.com/netbox-community/netbox/issues/17838) - Adjust `manage.py` to reference `python3` executable
 
 
 ---
 ---
 
 

+ 4 - 2
netbox/core/models/jobs.py

@@ -130,7 +130,7 @@ class Job(models.Model):
         super().clean()
         super().clean()
 
 
         # Validate the assigned object type
         # Validate the assigned object type
-        if self.object_type not in ObjectType.objects.with_feature('jobs'):
+        if self.object_type and self.object_type not in ObjectType.objects.with_feature('jobs'):
             raise ValidationError(
             raise ValidationError(
                 _("Jobs cannot be assigned to this object type ({type}).").format(type=self.object_type)
                 _("Jobs cannot be assigned to this object type ({type}).").format(type=self.object_type)
             )
             )
@@ -223,7 +223,7 @@ class Job(models.Model):
         rq_queue_name = get_queue_for_model(object_type.model if object_type else None)
         rq_queue_name = get_queue_for_model(object_type.model if object_type else None)
         queue = django_rq.get_queue(rq_queue_name)
         queue = django_rq.get_queue(rq_queue_name)
         status = JobStatusChoices.STATUS_SCHEDULED if schedule_at else JobStatusChoices.STATUS_PENDING
         status = JobStatusChoices.STATUS_SCHEDULED if schedule_at else JobStatusChoices.STATUS_PENDING
-        job = Job.objects.create(
+        job = Job(
             object_type=object_type,
             object_type=object_type,
             object_id=object_id,
             object_id=object_id,
             name=name,
             name=name,
@@ -233,6 +233,8 @@ class Job(models.Model):
             user=user,
             user=user,
             job_id=uuid.uuid4()
             job_id=uuid.uuid4()
         )
         )
+        job.full_clean()
+        job.save()
 
 
         # Run the job immediately, rather than enqueuing it as a background task. Note that this is a synchronous
         # Run the job immediately, rather than enqueuing it as a background task. Note that this is a synchronous
         # (blocking) operation, and execution will pause until the job completes.
         # (blocking) operation, and execution will pause until the job completes.

+ 1 - 1
netbox/dcim/models/cables.py

@@ -162,7 +162,7 @@ class Cable(PrimaryModel):
         if self.length is not None and not self.length_unit:
         if self.length is not None and not self.length_unit:
             raise ValidationError(_("Must specify a unit when setting a cable length"))
             raise ValidationError(_("Must specify a unit when setting a cable length"))
 
 
-        if self._state.adding and (not self.a_terminations or not self.b_terminations):
+        if self._state.adding and self.pk is None and (not self.a_terminations or not self.b_terminations):
             raise ValidationError(_("Must define A and B terminations when creating a new cable."))
             raise ValidationError(_("Must define A and B terminations when creating a new cable."))
 
 
         if self._terminations_modified:
         if self._terminations_modified:

+ 2 - 0
netbox/ipam/fields.py

@@ -105,6 +105,8 @@ IPAddressField.register_lookup(lookups.NetIn)
 IPAddressField.register_lookup(lookups.NetHostContained)
 IPAddressField.register_lookup(lookups.NetHostContained)
 IPAddressField.register_lookup(lookups.NetFamily)
 IPAddressField.register_lookup(lookups.NetFamily)
 IPAddressField.register_lookup(lookups.NetMaskLength)
 IPAddressField.register_lookup(lookups.NetMaskLength)
+IPAddressField.register_lookup(lookups.Host)
+IPAddressField.register_lookup(lookups.Inet)
 
 
 
 
 class ASNField(models.BigIntegerField):
 class ASNField(models.BigIntegerField):

+ 7 - 7
netbox/ipam/models/ip.py

@@ -626,15 +626,15 @@ class IPRange(ContactsMixin, PrimaryModel):
                 })
                 })
 
 
             # Check for overlapping ranges
             # Check for overlapping ranges
-            overlapping_range = IPRange.objects.exclude(pk=self.pk).filter(vrf=self.vrf).filter(
-                Q(start_address__gte=self.start_address, start_address__lte=self.end_address) |  # Starts inside
-                Q(end_address__gte=self.start_address, end_address__lte=self.end_address) |  # Ends inside
-                Q(start_address__lte=self.start_address, end_address__gte=self.end_address)  # Starts & ends outside
-            ).first()
-            if overlapping_range:
+            overlapping_ranges = IPRange.objects.exclude(pk=self.pk).filter(vrf=self.vrf).filter(
+                Q(start_address__host__inet__gte=self.start_address.ip, start_address__host__inet__lte=self.end_address.ip) |  # Starts inside
+                Q(end_address__host__inet__gte=self.start_address.ip, end_address__host__inet__lte=self.end_address.ip) |  # Ends inside
+                Q(start_address__host__inet__lte=self.start_address.ip, end_address__host__inet__gte=self.end_address.ip)  # Starts & ends outside
+            )
+            if overlapping_ranges.exists():
                 raise ValidationError(
                 raise ValidationError(
                     _("Defined addresses overlap with range {overlapping_range} in VRF {vrf}").format(
                     _("Defined addresses overlap with range {overlapping_range} in VRF {vrf}").format(
-                        overlapping_range=overlapping_range,
+                        overlapping_range=overlapping_ranges.first(),
                         vrf=self.vrf
                         vrf=self.vrf
                     ))
                     ))
 
 

+ 29 - 0
netbox/ipam/tests/test_models.py

@@ -36,6 +36,35 @@ class TestAggregate(TestCase):
         self.assertEqual(aggregate.get_utilization(), 100)
         self.assertEqual(aggregate.get_utilization(), 100)
 
 
 
 
+class TestIPRange(TestCase):
+
+    def test_overlapping_range(self):
+        iprange_192_168 = IPRange.objects.create(start_address=IPNetwork('192.168.0.1/22'), end_address=IPNetwork('192.168.0.49/22'))
+        iprange_192_168.clean()
+        iprange_3_1_99 = IPRange.objects.create(start_address=IPNetwork('1.2.3.1/24'), end_address=IPNetwork('1.2.3.99/24'))
+        iprange_3_1_99.clean()
+        iprange_3_100_199 = IPRange.objects.create(start_address=IPNetwork('1.2.3.100/24'), end_address=IPNetwork('1.2.3.199/24'))
+        iprange_3_100_199.clean()
+        iprange_3_200_255 = IPRange.objects.create(start_address=IPNetwork('1.2.3.200/24'), end_address=IPNetwork('1.2.3.255/24'))
+        iprange_3_200_255.clean()
+        iprange_4_1_99 = IPRange.objects.create(start_address=IPNetwork('1.2.4.1/24'), end_address=IPNetwork('1.2.4.99/24'))
+        iprange_4_1_99.clean()
+        iprange_4_200 = IPRange.objects.create(start_address=IPNetwork('1.2.4.200/24'), end_address=IPNetwork('1.2.4.255/24'))
+        iprange_4_200.clean()
+        # Overlapping range entirely within existing
+        with self.assertRaises(ValidationError):
+            iprange_3_123_124 = IPRange.objects.create(start_address=IPNetwork('1.2.3.123/26'), end_address=IPNetwork('1.2.3.124/26'))
+            iprange_3_123_124.clean()
+        # Overlapping range starting within existing
+        with self.assertRaises(ValidationError):
+            iprange_4_98_101 = IPRange.objects.create(start_address=IPNetwork('1.2.4.98/24'), end_address=IPNetwork('1.2.4.101/24'))
+            iprange_4_98_101.clean()
+        # Overlapping range ending within existing
+        with self.assertRaises(ValidationError):
+            iprange_4_198_201 = IPRange.objects.create(start_address=IPNetwork('1.2.4.198/24'), end_address=IPNetwork('1.2.4.201/24'))
+            iprange_4_198_201.clean()
+
+
 class TestPrefix(TestCase):
 class TestPrefix(TestCase):
 
 
     def test_get_duplicates(self):
     def test_get_duplicates(self):

+ 1 - 1
netbox/manage.py

@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 import os
 import os
 import sys
 import sys
 
 

+ 8 - 8
netbox/netbox/tests/test_jobs.py

@@ -5,7 +5,7 @@ from django.utils import timezone
 from django_rq import get_queue
 from django_rq import get_queue
 
 
 from ..jobs import *
 from ..jobs import *
-from core.models import Job
+from core.models import DataSource, Job
 from core.choices import JobStatusChoices
 from core.choices import JobStatusChoices
 
 
 
 
@@ -68,7 +68,7 @@ class EnqueueTest(JobRunnerTestCase):
     """
     """
 
 
     def test_enqueue(self):
     def test_enqueue(self):
-        instance = Job()
+        instance = DataSource()
         for i in range(1, 3):
         for i in range(1, 3):
             job = TestJobRunner.enqueue(instance, schedule_at=self.get_schedule_at())
             job = TestJobRunner.enqueue(instance, schedule_at=self.get_schedule_at())
 
 
@@ -76,13 +76,13 @@ class EnqueueTest(JobRunnerTestCase):
             self.assertEqual(TestJobRunner.get_jobs(instance).count(), i)
             self.assertEqual(TestJobRunner.get_jobs(instance).count(), i)
 
 
     def test_enqueue_once(self):
     def test_enqueue_once(self):
-        job = TestJobRunner.enqueue_once(instance=Job(), schedule_at=self.get_schedule_at())
+        job = TestJobRunner.enqueue_once(instance=DataSource(), schedule_at=self.get_schedule_at())
 
 
         self.assertIsInstance(job, Job)
         self.assertIsInstance(job, Job)
         self.assertEqual(job.name, TestJobRunner.__name__)
         self.assertEqual(job.name, TestJobRunner.__name__)
 
 
     def test_enqueue_once_twice_same(self):
     def test_enqueue_once_twice_same(self):
-        instance = Job()
+        instance = DataSource()
         schedule_at = self.get_schedule_at()
         schedule_at = self.get_schedule_at()
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at)
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at)
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at)
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at)
@@ -91,7 +91,7 @@ class EnqueueTest(JobRunnerTestCase):
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 1)
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 1)
 
 
     def test_enqueue_once_twice_different_schedule_at(self):
     def test_enqueue_once_twice_different_schedule_at(self):
-        instance = Job()
+        instance = DataSource()
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at())
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at())
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at(2))
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at(2))
 
 
@@ -100,7 +100,7 @@ class EnqueueTest(JobRunnerTestCase):
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 1)
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 1)
 
 
     def test_enqueue_once_twice_different_interval(self):
     def test_enqueue_once_twice_different_interval(self):
-        instance = Job()
+        instance = DataSource()
         schedule_at = self.get_schedule_at()
         schedule_at = self.get_schedule_at()
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at)
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at)
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at, interval=60)
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=schedule_at, interval=60)
@@ -112,7 +112,7 @@ class EnqueueTest(JobRunnerTestCase):
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 1)
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 1)
 
 
     def test_enqueue_once_with_enqueue(self):
     def test_enqueue_once_with_enqueue(self):
-        instance = Job()
+        instance = DataSource()
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at(2))
         job1 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at(2))
         job2 = TestJobRunner.enqueue(instance, schedule_at=self.get_schedule_at())
         job2 = TestJobRunner.enqueue(instance, schedule_at=self.get_schedule_at())
 
 
@@ -120,7 +120,7 @@ class EnqueueTest(JobRunnerTestCase):
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 2)
         self.assertEqual(TestJobRunner.get_jobs(instance).count(), 2)
 
 
     def test_enqueue_once_after_enqueue(self):
     def test_enqueue_once_after_enqueue(self):
-        instance = Job()
+        instance = DataSource()
         job1 = TestJobRunner.enqueue(instance, schedule_at=self.get_schedule_at())
         job1 = TestJobRunner.enqueue(instance, schedule_at=self.get_schedule_at())
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at(2))
         job2 = TestJobRunner.enqueue_once(instance, schedule_at=self.get_schedule_at(2))
 
 

+ 1 - 1
netbox/project-static/package.json

@@ -30,7 +30,7 @@
     "gridstack": "10.3.1",
     "gridstack": "10.3.1",
     "htmx.org": "1.9.12",
     "htmx.org": "1.9.12",
     "query-string": "9.1.1",
     "query-string": "9.1.1",
-    "sass": "1.79.5",
+    "sass": "1.80.4",
     "tom-select": "2.3.1",
     "tom-select": "2.3.1",
     "typeface-inter": "3.18.1",
     "typeface-inter": "3.18.1",
     "typeface-roboto-mono": "1.1.13"
     "typeface-roboto-mono": "1.1.13"

+ 4 - 4
netbox/project-static/yarn.lock

@@ -2656,10 +2656,10 @@ safe-regex-test@^1.0.3:
     es-errors "^1.3.0"
     es-errors "^1.3.0"
     is-regex "^1.1.4"
     is-regex "^1.1.4"
 
 
-sass@1.79.5:
-  version "1.79.5"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.79.5.tgz#646c627601cd5f84c64f7b1485b9292a313efae4"
-  integrity sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==
+sass@1.80.4:
+  version "1.80.4"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.80.4.tgz#bc0418fd796cad2f1a1309d8b4d7fe44b7027de0"
+  integrity sha512-rhMQ2tSF5CsuuspvC94nPM9rToiAFw2h3JTrLlgmNw1MH79v8Cr3DH6KF6o6r+8oofY3iYVPUf66KzC8yuVN1w==
   dependencies:
   dependencies:
     "@parcel/watcher" "^2.4.1"
     "@parcel/watcher" "^2.4.1"
     chokidar "^4.0.0"
     chokidar "^4.0.0"

+ 2 - 2
netbox/release.yaml

@@ -1,3 +1,3 @@
-version: "4.1.4"
+version: "4.1.5"
 edition: "Community"
 edition: "Community"
-published: "2024-10-15"
+published: "2024-10-28"

+ 1 - 14
netbox/templates/dcim/devicetype/component_templates.html

@@ -18,21 +18,8 @@
             <button type="submit" name="_rename"
             <button type="submit" name="_rename"
                     {% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
                     {% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
                     class="btn btn-outline-warning">
                     class="btn btn-outline-warning">
-                <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
+                <i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename Selected
             </button>
             </button>
         {% endif %}
         {% endif %}
     {% endwith %}
     {% endwith %}
 {% endblock bulk_edit_controls %}
 {% endblock bulk_edit_controls %}
-
-{% block bulk_extra_controls %}
-    {{ block.super }}
-    {% if request.user|can_add:child_model %}
-        <div class="bulk-button-group">
-            <a href="{% url table.Meta.model|viewname:"add" %}?device_type={{ object.pk }}&return_url={{ return_url }}" class="btn btn-primary">
-                <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
-                {% trans "Add" %} {{ title }}
-            </a>
-        </div>
-    {% endif %}
-{% endblock bulk_extra_controls %}
-

+ 2 - 0
netbox/templates/dcim/inc/devicetype_breadcrumbs.html

@@ -0,0 +1,2 @@
+
+<li class="breadcrumb-item"><a href="{% url 'dcim:devicetype_list' %}?manufacturer_id={{ object.manufacturer.pk }}">{{ object.manufacturer }}</a></li>

+ 38 - 0
netbox/templates/dcim/inc/moduletype_buttons.html

@@ -0,0 +1,38 @@
+{% load buttons %}
+{% load helpers %}
+{% load i18n %}
+
+
+{% if perms.dcim.change_devicetype %}
+  <div class="dropdown">
+    <button type="button" class="btn btn-primary dropdown-toggle"data-bs-toggle="dropdown" 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_consoleporttemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_consoleports' pk=object.pk %}">{% trans "Console Ports" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_consoleserverporttemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_consoleserverports' pk=object.pk %}">{% trans "Console Server Ports" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_powerporttemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_powerports' pk=object.pk %}">{% trans "Power Ports" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_poweroutlettemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_poweroutlets' pk=object.pk %}">{% trans "Power Outlets" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_interfacetemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_interfaces' pk=object.pk %}">{% trans "Interfaces" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_frontporttemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_frontports' pk=object.pk %}">{% trans "Front Ports" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_rearporttemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_rearports' pk=object.pk %}">{% trans "Rear Ports" %}</a></li>
+      {% endif %}
+      {% if perms.dcim.add_modulebaytemplate %}
+        <li><a class="dropdown-item" href="{% url 'dcim:modulebaytemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_modulebays' pk=object.pk %}">{% trans "Module Bays" %}</a></li>
+      {% endif %}
+    </ul>
+  </div>
+{% endif %}

+ 12 - 1
netbox/templates/dcim/moduletype.html

@@ -1,9 +1,20 @@
-{% extends 'dcim/moduletype/base.html' %}
+{% extends 'generic/object.html' %}
 {% load buttons %}
 {% load buttons %}
 {% load helpers %}
 {% load helpers %}
 {% load plugins %}
 {% load plugins %}
 {% load i18n %}
 {% load i18n %}
 
 
+{% block title %}{{ object.manufacturer }} {{ object.model }}{% endblock %}
+
+{% block breadcrumbs %}
+  {{ block.super }}
+  {%  include 'dcim/inc/devicetype_breadcrumbs.html' %}
+{% endblock %}
+
+{% block extra_controls %}
+  {%  include 'dcim/inc/moduletype_buttons.html' %}
+{% endblock %}
+
 {% block content %}
 {% block content %}
   <div class="row">
   <div class="row">
     <div class="col col-md-6">
     <div class="col col-md-6">

+ 0 - 48
netbox/templates/dcim/moduletype/base.html

@@ -1,48 +0,0 @@
-{% extends 'generic/object.html' %}
-{% load buttons %}
-{% load helpers %}
-{% load plugins %}
-{% load i18n %}
-
-{% block title %}{{ object.manufacturer }} {{ object.model }}{% endblock %}
-
-{% block breadcrumbs %}
-  {{ block.super }}
-  <li class="breadcrumb-item"><a href="{% url 'dcim:moduletype_list' %}?manufacturer_id={{ object.manufacturer.pk }}">{{ object.manufacturer }}</a></li>
-{% endblock %}
-
-{% block extra_controls %}
-  {% if perms.dcim.change_devicetype %}
-    <div class="dropdown">
-      <button type="button" class="btn btn-primary dropdown-toggle"data-bs-toggle="dropdown" 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_consoleporttemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:consoleporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_consoleports' pk=object.pk %}">{% trans "Console Ports" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_consoleserverporttemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:consoleserverporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_consoleserverports' pk=object.pk %}">{% trans "Console Server Ports" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_powerporttemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:powerporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_powerports' pk=object.pk %}">{% trans "Power Ports" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_poweroutlettemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:poweroutlettemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_poweroutlets' pk=object.pk %}">{% trans "Power Outlets" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_interfacetemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:interfacetemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_interfaces' pk=object.pk %}">{% trans "Interfaces" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_frontporttemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:frontporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_frontports' pk=object.pk %}">{% trans "Front Ports" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_rearporttemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:rearporttemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_rearports' pk=object.pk %}">{% trans "Rear Ports" %}</a></li>
-        {% endif %}
-        {% if perms.dcim.add_modulebaytemplate %}
-          <li><a class="dropdown-item" href="{% url 'dcim:modulebaytemplate_add' %}?module_type={{ object.pk }}&return_url={% url 'dcim:moduletype_modulebays' pk=object.pk %}">{% trans "Module Bays" %}</a></li>
-        {% endif %}
-      </ul>
-    </div>
-  {% endif %}
-{% endblock %}

+ 33 - 40
netbox/templates/dcim/moduletype/component_templates.html

@@ -1,44 +1,37 @@
-{% extends 'dcim/moduletype/base.html' %}
+{% extends 'generic/object_children.html' %}
 {% load render_table from django_tables2 %}
 {% load render_table from django_tables2 %}
 {% load helpers %}
 {% load helpers %}
 {% load i18n %}
 {% load i18n %}
 
 
-{% block content %}
-  {% if perms.dcim.change_moduletype %}
-    <form method="post">
-        {% csrf_token %}
-        <div class="card">
-            <div class="htmx-container table-responsive" id="object_list">
-              {% include 'htmx/table.html' %}
-            </div>
-            <div class="card-footer d-print-none">
-                {% if table.rows %}
-                    <button type="submit" name="_edit" {% formaction %}="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ return_url }}" class="btn btn-warning">
-                        <span class="mdi mdi-pencil-outline" aria-hidden="true"></span> {% trans "Rename" %}
-                    </button>
-                    <button type="submit" name="_edit" {% formaction %}="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ return_url }}" class="btn btn-warning">
-                        <span class="mdi mdi-pencil" aria-hidden="true"></span> {% trans "Edit" %}
-                    </button>
-                    <button type="submit" name="_delete" {% formaction %}="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ return_url }}" class="btn btn-danger">
-                        <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
-                    </button>
-                {% endif %}
-                <div class="float-end">
-                    <a href="{% url table.Meta.model|viewname:"add" %}?module_type={{ object.pk }}&return_url={{ return_url }}" class="btn btn-primary">
-                        <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
-                        {% trans "Add" %} {{ title }}
-                    </a>
-                </div>
-                <div class="clearfix"></div>
-            </div>
-        </div>
-    </form>
-  {% else %}
-    <div class="card">
-      <h2 class="card-header">{{ title }}</h2>
-      <div class="htmx-container table-responsive" id="object_list">
-        {% include 'htmx/table.html' %}
-      </div>
-    </div>
-  {% endif %}
-{% endblock content %}
+{% block title %}{{ object.manufacturer }} {{ object.model }}{% endblock %}
+
+{% block breadcrumbs %}
+  {{ block.super }}
+  {%  include 'dcim/inc/devicetype_breadcrumbs.html' %}
+{% endblock %}
+
+{% 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 %}
+

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1540 - 1871
netbox/translations/cs/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/da/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/de/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 3034 - 3555
netbox/translations/en/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/es/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/fr/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/it/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2065 - 2465
netbox/translations/ja/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/nl/LC_MESSAGES/django.po


BIN
netbox/translations/pl/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1546 - 1877
netbox/translations/pl/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/pt/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/ru/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1118 - 1387
netbox/translations/tr/LC_MESSAGES/django.po


BIN
netbox/translations/uk/LC_MESSAGES/django.mo


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1541 - 1872
netbox/translations/uk/LC_MESSAGES/django.po


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2464 - 2911
netbox/translations/zh/LC_MESSAGES/django.po


+ 7 - 6
requirements.txt

@@ -2,13 +2,13 @@ Django==5.1.2
 django-cors-headers==4.5.0
 django-cors-headers==4.5.0
 django-debug-toolbar==4.4.6
 django-debug-toolbar==4.4.6
 django-filter==24.3
 django-filter==24.3
-django-htmx==1.19.0
+django-htmx==1.21.0
 django-graphiql-debug-toolbar==0.2.0
 django-graphiql-debug-toolbar==0.2.0
 django-mptt==0.16.0
 django-mptt==0.16.0
 django-pglocks==1.0.4
 django-pglocks==1.0.4
 django-prometheus==2.3.1
 django-prometheus==2.3.1
 django-redis==5.4.0
 django-redis==5.4.0
-django-rich==1.11.0
+django-rich==1.12.0
 django-rq==2.10.2
 django-rq==2.10.2
 django-taggit==6.1.0
 django-taggit==6.1.0
 django-tables2==2.7.0
 django-tables2==2.7.0
@@ -20,18 +20,19 @@ feedparser==6.0.11
 gunicorn==23.0.0
 gunicorn==23.0.0
 Jinja2==3.1.4
 Jinja2==3.1.4
 Markdown==3.7
 Markdown==3.7
-mkdocs-material==9.5.41
+mkdocs-material==9.5.42
 mkdocstrings[python-legacy]==0.26.2
 mkdocstrings[python-legacy]==0.26.2
 netaddr==1.3.0
 netaddr==1.3.0
 nh3==0.2.18
 nh3==0.2.18
-Pillow==10.4.0
+Pillow==11.0.0
 psycopg[c,pool]==3.2.3
 psycopg[c,pool]==3.2.3
 PyYAML==6.0.2
 PyYAML==6.0.2
 requests==2.32.3
 requests==2.32.3
+rq==1.16.2
 social-auth-app-django==5.4.2
 social-auth-app-django==5.4.2
 social-auth-core==4.5.4
 social-auth-core==4.5.4
-strawberry-graphql==0.246.2
-strawberry-graphql-django==0.48.0
+strawberry-graphql==0.247.0
+strawberry-graphql-django==0.49.1
 svgwrite==1.4.3
 svgwrite==1.4.3
 tablib==3.7.0
 tablib==3.7.0
 tzdata==2024.2
 tzdata==2024.2

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است