Просмотр исходного кода

Merge pull request #17346 from netbox-community/develop

Release v4.0.11
Jeremy Stretch 1 год назад
Родитель
Сommit
0b120e6ad2
49 измененных файлов с 3278 добавлено и 3222 удалено
  1. 1 1
      .github/ISSUE_TEMPLATE/bug_report.yaml
  2. 1 1
      .github/ISSUE_TEMPLATE/feature_request.yaml
  3. 10 0
      docs/release-notes/version-4.0.md
  4. 4 0
      netbox/circuits/tests/test_api.py
  5. 1 0
      netbox/core/tests/test_api.py
  6. 1 1
      netbox/dcim/tables/devices.py
  7. 27 0
      netbox/dcim/tests/test_api.py
  8. 5 2
      netbox/extras/dashboard/widgets.py
  9. 8 9
      netbox/extras/management/commands/runscript.py
  10. 1 0
      netbox/ipam/tests/test_api.py
  11. 1 1
      netbox/netbox/graphql/schema.py
  12. 1 1
      netbox/netbox/settings.py
  13. 12 1
      netbox/netbox/tests/test_graphql.py
  14. 6 13
      netbox/project-static/yarn.lock
  15. 1 1
      netbox/templates/extras/dashboard/widgets/objectcounts.html
  16. 1 0
      netbox/tenancy/tests/test_api.py
  17. BIN
      netbox/translations/cs/LC_MESSAGES/django.mo
  18. 212 212
      netbox/translations/cs/LC_MESSAGES/django.po
  19. BIN
      netbox/translations/da/LC_MESSAGES/django.mo
  20. 212 212
      netbox/translations/da/LC_MESSAGES/django.po
  21. BIN
      netbox/translations/de/LC_MESSAGES/django.mo
  22. 212 212
      netbox/translations/de/LC_MESSAGES/django.po
  23. 212 212
      netbox/translations/en/LC_MESSAGES/django.po
  24. BIN
      netbox/translations/es/LC_MESSAGES/django.mo
  25. 212 212
      netbox/translations/es/LC_MESSAGES/django.po
  26. BIN
      netbox/translations/fr/LC_MESSAGES/django.mo
  27. 212 212
      netbox/translations/fr/LC_MESSAGES/django.po
  28. BIN
      netbox/translations/it/LC_MESSAGES/django.mo
  29. 212 212
      netbox/translations/it/LC_MESSAGES/django.po
  30. BIN
      netbox/translations/ja/LC_MESSAGES/django.mo
  31. 212 212
      netbox/translations/ja/LC_MESSAGES/django.po
  32. BIN
      netbox/translations/nl/LC_MESSAGES/django.mo
  33. 212 212
      netbox/translations/nl/LC_MESSAGES/django.po
  34. BIN
      netbox/translations/pl/LC_MESSAGES/django.mo
  35. 212 212
      netbox/translations/pl/LC_MESSAGES/django.po
  36. BIN
      netbox/translations/pt/LC_MESSAGES/django.mo
  37. 216 216
      netbox/translations/pt/LC_MESSAGES/django.po
  38. BIN
      netbox/translations/ru/LC_MESSAGES/django.mo
  39. 214 214
      netbox/translations/ru/LC_MESSAGES/django.po
  40. BIN
      netbox/translations/tr/LC_MESSAGES/django.mo
  41. 212 212
      netbox/translations/tr/LC_MESSAGES/django.po
  42. BIN
      netbox/translations/uk/LC_MESSAGES/django.mo
  43. 212 212
      netbox/translations/uk/LC_MESSAGES/django.po
  44. BIN
      netbox/translations/zh/LC_MESSAGES/django.mo
  45. 212 212
      netbox/translations/zh/LC_MESSAGES/django.po
  46. 2 0
      netbox/virtualization/tests/test_api.py
  47. 3 0
      netbox/vpn/tests/test_api.py
  48. 1 0
      netbox/wireless/tests/test_api.py
  49. 5 5
      requirements.txt

+ 1 - 1
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -26,7 +26,7 @@ body:
     attributes:
       label: NetBox Version
       description: What version of NetBox are you currently running?
-      placeholder: v4.0.10
+      placeholder: v4.0.11
     validations:
       required: true
   - type: dropdown

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
       label: NetBox version
       description: What version of NetBox are you currently running?
-      placeholder: v4.0.10
+      placeholder: v4.0.11
     validations:
       required: true
   - type: dropdown

+ 10 - 0
docs/release-notes/version-4.0.md

@@ -1,5 +1,15 @@
 # NetBox v4.0
 
+## v4.0.11 (2024-09-03)
+
+### Bug Fixes
+
+* [#17310](https://github.com/netbox-community/netbox/issues/17310) - Enforce restricted queryset for related objects in GraphQL API requests
+* [#17321](https://github.com/netbox-community/netbox/issues/17321) - Ensure the job is attributed to the specified user when using the `runscript` management command
+* [#17323](https://github.com/netbox-community/netbox/issues/17323) - Associate job with script object when executed using the `runscript` management command
+* [#17337](https://github.com/netbox-community/netbox/issues/17337) - Fix ordering of virtual device contexts by device name
+* [#17341](https://github.com/netbox-community/netbox/issues/17341) - Avoid `NoReverseMatch` exceptions with specific dashboard widget configurations
+
 ## v4.0.10 (2024-08-29)
 
 ### Enhancements

+ 4 - 0
netbox/circuits/tests/test_api.py

@@ -96,6 +96,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'status': 'planned',
     }
+    user_permissions = ('circuits.view_provider', 'circuits.view_circuittype')
 
     @classmethod
     def setUpTestData(cls):
@@ -150,6 +151,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
 class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
     model = CircuitTermination
     brief_fields = ['_occupied', 'cable', 'circuit', 'description', 'display', 'id', 'term_side', 'url']
+    user_permissions = ('circuits.view_circuit', )
 
     @classmethod
     def setUpTestData(cls):
@@ -209,6 +211,7 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
 class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
     model = ProviderAccount
     brief_fields = ['account', 'description', 'display', 'id', 'name', 'url']
+    user_permissions = ('circuits.view_provider', )
 
     @classmethod
     def setUpTestData(cls):
@@ -252,6 +255,7 @@ class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
 class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
     model = ProviderNetwork
     brief_fields = ['description', 'display', 'id', 'name', 'url']
+    user_permissions = ('circuits.view_provider', )
 
     @classmethod
     def setUpTestData(cls):

+ 1 - 0
netbox/core/tests/test_api.py

@@ -57,6 +57,7 @@ class DataFileTest(
 ):
     model = DataFile
     brief_fields = ['display', 'id', 'path', 'url']
+    user_permissions = ('core.view_datasource', )
 
     @classmethod
     def setUpTestData(cls):

+ 1 - 1
netbox/dcim/tables/devices.py

@@ -1031,7 +1031,7 @@ class VirtualDeviceContextTable(TenancyColumnsMixin, NetBoxTable):
     )
     device = tables.TemplateColumn(
         verbose_name=_('Device'),
-        order_by=('_name',),
+        order_by=('device___name',),
         template_code=DEVICE_LINK,
         linkify=True
     )

+ 27 - 0
netbox/dcim/tests/test_api.py

@@ -195,6 +195,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_site', )
 
     @classmethod
     def setUpTestData(cls):
@@ -280,6 +281,7 @@ class RackTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'status': 'planned',
     }
+    user_permissions = ('dcim.view_site', )
 
     @classmethod
     def setUpTestData(cls):
@@ -368,6 +370,7 @@ class RackReservationTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_rack', 'users.view_user')
 
     @classmethod
     def setUpTestData(cls):
@@ -447,6 +450,7 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'part_number': 'ABC123',
     }
+    user_permissions = ('dcim.view_manufacturer', )
 
     @classmethod
     def setUpTestData(cls):
@@ -492,6 +496,7 @@ class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'part_number': 'ABC123',
     }
+    user_permissions = ('dcim.view_manufacturer', )
 
     @classmethod
     def setUpTestData(cls):
@@ -663,6 +668,7 @@ class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_devicetype', )
 
     @classmethod
     def setUpTestData(cls):
@@ -768,6 +774,7 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_rearporttemplate', )
 
     @classmethod
     def setUpTestData(cls):
@@ -905,6 +912,7 @@ class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_devicetype', )
 
     @classmethod
     def setUpTestData(cls):
@@ -945,6 +953,7 @@ class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_devicetype', )
 
     @classmethod
     def setUpTestData(cls):
@@ -985,6 +994,7 @@ class InventoryItemTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_devicetype', 'dcim.view_manufacturer',)
 
     @classmethod
     def setUpTestData(cls):
@@ -1103,6 +1113,10 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'status': 'failed',
     }
+    user_permissions = (
+        'dcim.view_site', 'dcim.view_rack', 'dcim.view_location', 'dcim.view_devicerole', 'dcim.view_devicetype',
+        'extras.view_configtemplate',
+    )
 
     @classmethod
     def setUpTestData(cls):
@@ -1293,6 +1307,7 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'serial': '1234ABCD',
     }
+    user_permissions = ('dcim.view_modulebay', 'dcim.view_moduletype', 'dcim.view_device')
 
     @classmethod
     def setUpTestData(cls):
@@ -1358,6 +1373,7 @@ class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
         'description': 'New description',
     }
     peer_termination_type = ConsoleServerPort
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1400,6 +1416,7 @@ class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIView
         'description': 'New description',
     }
     peer_termination_type = ConsolePort
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1442,6 +1459,7 @@ class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
         'description': 'New description',
     }
     peer_termination_type = PowerOutlet
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1481,6 +1499,7 @@ class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
         'description': 'New description',
     }
     peer_termination_type = PowerPort
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1529,6 +1548,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
         'description': 'New description',
     }
     peer_termination_type = Interface
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1663,6 +1683,7 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase):
         'description': 'New description',
     }
     peer_termination_type = Interface
+    user_permissions = ('dcim.view_device', 'dcim.view_rearport')
 
     @classmethod
     def setUpTestData(cls):
@@ -1721,6 +1742,7 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
         'description': 'New description',
     }
     peer_termination_type = Interface
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1762,6 +1784,7 @@ class ModuleBayTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1801,6 +1824,7 @@ class DeviceBayTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_device', )
 
     @classmethod
     def setUpTestData(cls):
@@ -1864,6 +1888,7 @@ class InventoryItemTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'description': 'New description',
     }
+    user_permissions = ('dcim.view_device', 'dcim.view_manufacturer')
 
     @classmethod
     def setUpTestData(cls):
@@ -2160,6 +2185,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
 class PowerPanelTest(APIViewTestCases.APIViewTestCase):
     model = PowerPanel
     brief_fields = ['description', 'display', 'id', 'name', 'powerfeed_count', 'url']
+    user_permissions = ('dcim.view_site', )
 
     @classmethod
     def setUpTestData(cls):
@@ -2212,6 +2238,7 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'status': 'planned',
     }
+    user_permissions = ('dcim.view_powerpanel', )
 
     @classmethod
     def setUpTestData(cls):

+ 5 - 2
netbox/extras/dashboard/widgets.py

@@ -183,10 +183,13 @@ class ObjectCountsWidget(DashboardWidget):
         for model in get_models_from_content_types(self.config['models']):
             permission = get_permission_for_model(model, 'view')
             if request.user.has_perm(permission):
-                url = reverse(get_viewname(model, 'list'))
+                try:
+                    url = reverse(get_viewname(model, 'list'))
+                except NoReverseMatch:
+                    url = None
                 qs = model.objects.restrict(request.user, 'view')
                 # Apply any specified filters
-                if filters := self.config.get('filters'):
+                if url and (filters := self.config.get('filters')):
                     params = dict_to_querydict(filters)
                     filterset = getattr(resolve(url).func.view_class, 'filterset', None)
                     qs = filterset(params, qs).qs

+ 8 - 9
netbox/extras/management/commands/runscript.py

@@ -34,7 +34,7 @@ class Command(BaseCommand):
 
     def handle(self, *args, **options):
 
-        def _run_script():
+        def _run_script(script):
             """
             Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with
             the event_tracking context manager (which is bypassed if commit == False).
@@ -85,7 +85,6 @@ class Command(BaseCommand):
 
         module_name, script_name = script.split('.', 1)
         module, script = get_module_and_script(module_name, script_name)
-        script = script.python_class
 
         # Take user from command line if provided and exists, other
         if options['user']:
@@ -102,7 +101,7 @@ class Command(BaseCommand):
         stdouthandler.setLevel(logging.DEBUG)
         stdouthandler.setFormatter(formatter)
 
-        logger = logging.getLogger(f"netbox.scripts.{script.full_name}")
+        logger = logging.getLogger(f"netbox.scripts.{script.python_class.full_name}")
         logger.addHandler(stdouthandler)
 
         try:
@@ -118,14 +117,14 @@ class Command(BaseCommand):
             raise CommandError(f"Invalid log level: {loglevel}")
 
         # Initialize the script form
-        script = script()
-        form = script.as_form(data, None)
+        script_instance = script.python_class()
+        form = script_instance.as_form(data, None)
 
         # Create the job
         job = Job.objects.create(
-            object=module,
-            name=script.class_name,
-            user=User.objects.filter(is_superuser=True).order_by('pk')[0],
+            object=script,
+            name=script_instance.class_name,
+            user=user,
             job_id=uuid.uuid4()
         )
 
@@ -149,7 +148,7 @@ class Command(BaseCommand):
             # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
             # change logging, webhooks, etc.
             with event_tracking(request):
-                _run_script()
+                _run_script(script_instance)
         else:
             logger.error('Data is not valid:')
             for field, errors in form.errors.get_json_data().items():

+ 1 - 0
netbox/ipam/tests/test_api.py

@@ -767,6 +767,7 @@ class FHRPGroupAssignmentTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'priority': 100,
     }
+    user_permissions = ('ipam.view_fhrpgroup', )
 
     @classmethod
     def setUpTestData(cls):

+ 1 - 1
netbox/netbox/graphql/schema.py

@@ -36,6 +36,6 @@ schema = strawberry.Schema(
     query=Query,
     config=StrawberryConfig(auto_camel_case=False),
     extensions=[
-        DjangoOptimizerExtension,
+        DjangoOptimizerExtension(prefetch_custom_queryset=True),
     ]
 )

+ 1 - 1
netbox/netbox/settings.py

@@ -25,7 +25,7 @@ from utilities.string import trailing_slash
 # Environment setup
 #
 
-VERSION = '4.0.10'
+VERSION = '4.0.11'
 HOSTNAME = platform.node()
 # Set the base directory two levels up
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

+ 12 - 1
netbox/netbox/tests/test_graphql.py

@@ -6,6 +6,7 @@ from rest_framework import status
 
 from core.models import ObjectType
 from dcim.models import Site, Location
+from ipam.models import ASN, RIR
 from users.models import ObjectPermission
 from utilities.testing import disable_warnings, APITestCase, TestCase
 
@@ -45,7 +46,6 @@ class GraphQLTestCase(TestCase):
 class GraphQLAPITestCase(APITestCase):
 
     @override_settings(LOGIN_REQUIRED=True)
-    @override_settings(EXEMPT_VIEW_PERMISSIONS=['*', 'auth.user'])
     def test_graphql_filter_objects(self):
         """
         Test the operation of filters for GraphQL API requests.
@@ -66,6 +66,7 @@ class GraphQLAPITestCase(APITestCase):
         obj_perm.save()
         obj_perm.users.add(self.user)
         obj_perm.object_types.add(ObjectType.objects.get_for_model(Location))
+        obj_perm.object_types.add(ObjectType.objects.get_for_model(Site))
 
         # A valid request should return the filtered list
         url = reverse('graphql')
@@ -75,6 +76,7 @@ class GraphQLAPITestCase(APITestCase):
         data = json.loads(response.content)
         self.assertNotIn('errors', data)
         self.assertEqual(len(data['data']['location_list']), 1)
+        self.assertIsNotNone(data['data']['location_list'][0]['site'])
 
         # An invalid request should return an empty list
         query = '{location_list(filters: {site_id: "99999"}) {id site {id}}}'  # Invalid site ID
@@ -82,3 +84,12 @@ class GraphQLAPITestCase(APITestCase):
         self.assertHttpStatus(response, status.HTTP_200_OK)
         data = json.loads(response.content)
         self.assertEqual(len(data['data']['location_list']), 0)
+
+        # Removing the permissions from location should result in an empty locations list
+        obj_perm.object_types.remove(ObjectType.objects.get_for_model(Location))
+        query = '{site(id: ' + str(sites[0].pk) + ') {id locations {id}}}'
+        response = self.client.post(url, data={'query': query}, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        data = json.loads(response.content)
+        self.assertNotIn('errors', data)
+        self.assertEqual(len(data['data']['site']['locations']), 0)

+ 6 - 13
netbox/project-static/yarn.lock

@@ -986,14 +986,7 @@ brace-expansion@^2.0.1:
   dependencies:
     balanced-match "^1.0.0"
 
-braces@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
-  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
-  dependencies:
-    fill-range "^7.0.1"
-
-braces@~3.0.2:
+braces@^3.0.3, braces@~3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
   integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
@@ -1564,7 +1557,7 @@ file-entry-cache@^6.0.1:
   dependencies:
     flat-cache "^3.0.4"
 
-fill-range@^7.0.1, fill-range@^7.1.1:
+fill-range@^7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
   integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
@@ -2174,11 +2167,11 @@ meros@^1.1.4:
   integrity sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==
 
 micromatch@^4.0.4:
-  version "4.0.5"
-  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
-  integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
+  integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
   dependencies:
-    braces "^3.0.2"
+    braces "^3.0.3"
     picomatch "^2.3.1"
 
 minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:

+ 1 - 1
netbox/templates/extras/dashboard/widgets/objectcounts.html

@@ -3,7 +3,7 @@
 {% if counts %}
   <div class="list-group list-group-flush">
     {% for model, count, url in counts %}
-      <a href="{{ url }}" class="list-group-item list-group-item-action px-1 py-2">
+      <a {% if url %}href="{{ url }}" {% endif %}class="list-group-item list-group-item-action px-1 py-2">
         <div class="d-flex w-100 justify-content-between align-items-center">
           {{ model|meta:"verbose_name_plural"|bettertitle }}
           {% if count is None %}

+ 1 - 0
netbox/tenancy/tests/test_api.py

@@ -210,6 +210,7 @@ class ContactAssignmentTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'priority': ContactPriorityChoices.PRIORITY_INACTIVE,
     }
+    user_permissions = ('tenancy.view_contact', )
 
     @classmethod
     def setUpTestData(cls):

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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/cs/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/da/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/de/LC_MESSAGES/django.po


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/en/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/es/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/fr/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/it/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/ja/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/nl/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/pl/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 216 - 216
netbox/translations/pt/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 214 - 214
netbox/translations/ru/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/tr/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/uk/LC_MESSAGES/django.po


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


Разница между файлами не показана из-за своего большого размера
+ 212 - 212
netbox/translations/zh/LC_MESSAGES/django.po


+ 2 - 0
netbox/virtualization/tests/test_api.py

@@ -253,6 +253,7 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
         'description': 'New description',
     }
     graphql_base_name = 'vm_interface'
+    user_permissions = ('virtualization.view_virtualmachine', )
 
     @classmethod
     def setUpTestData(cls):
@@ -342,6 +343,7 @@ class VirtualDiskTest(APIViewTestCases.APIViewTestCase):
         'size': 888,
     }
     graphql_base_name = 'virtual_disk'
+    user_permissions = ('virtualization.view_virtualmachine', )
 
     @classmethod
     def setUpTestData(cls):

+ 3 - 0
netbox/vpn/tests/test_api.py

@@ -116,6 +116,7 @@ class TunnelTerminationTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'role': TunnelTerminationRoleChoices.ROLE_PEER,
     }
+    user_permissions = ('vpn.view_tunnel', )
 
     @classmethod
     def setUpTestData(cls):
@@ -430,6 +431,7 @@ class IPSecPolicyTest(APIViewTestCases.APIViewTestCase):
 class IPSecProfileTest(APIViewTestCases.APIViewTestCase):
     model = IPSecProfile
     brief_fields = ['description', 'display', 'id', 'name', 'url']
+    user_permissions = ('vpn.view_ikepolicy', 'vpn.view_ipsecpolicy')
 
     @classmethod
     def setUpTestData(cls):
@@ -558,6 +560,7 @@ class L2VPNTest(APIViewTestCases.APIViewTestCase):
 class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase):
     model = L2VPNTermination
     brief_fields = ['display', 'id', 'l2vpn', 'url']
+    user_permissions = ('dcim.view_location', 'vpn.view_l2vpn')
 
     @classmethod
     def setUpTestData(cls):

+ 1 - 0
netbox/wireless/tests/test_api.py

@@ -114,6 +114,7 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
         'status': 'planned',
     }
+    user_permissions = ('dcim.view_interface', )
 
     @classmethod
     def setUpTestData(cls):

+ 5 - 5
requirements.txt

@@ -1,4 +1,4 @@
-Django==5.0.8
+Django==5.0.9
 django-cors-headers==4.4.0
 django-debug-toolbar==4.4.6
 django-filter==24.2
@@ -8,7 +8,7 @@ django-mptt==0.16.0
 django-pglocks==1.0.4
 django-prometheus==2.3.1
 django-redis==5.4.0
-django-rich==1.10.0
+django-rich==1.11.0
 django-rq==2.10.2
 django-taggit==5.0.1
 django-tables2==2.7.0
@@ -20,8 +20,8 @@ feedparser==6.0.11
 gunicorn==23.0.0
 Jinja2==3.1.4
 Markdown==3.7
-mkdocs-material==9.5.33
-mkdocstrings[python-legacy]==0.25.2
+mkdocs-material==9.5.34
+mkdocstrings[python-legacy]==0.26.0
 netaddr==1.3.0
 nh3==0.2.18
 Pillow==10.4.0
@@ -30,7 +30,7 @@ PyYAML==6.0.2
 requests==2.32.3
 social-auth-app-django==5.4.2
 social-auth-core==4.5.4
-strawberry-graphql==0.237.2
+strawberry-graphql==0.239.2
 strawberry-graphql-django==0.47.1
 svgwrite==1.4.3
 tablib==3.6.1

Некоторые файлы не были показаны из-за большого количества измененных файлов