Przeglądaj źródła

Merge branch 'develop' into feature

Jeremy Stretch 1 rok temu
rodzic
commit
a777850702
47 zmienionych plików z 3305 dodań i 3237 usunięć
  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. 5 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. 28 0
      netbox/dcim/tests/test_api.py
  8. 5 2
      netbox/extras/dashboard/widgets.py
  9. 1 0
      netbox/ipam/tests/test_api.py
  10. 1 1
      netbox/netbox/graphql/schema.py
  11. 12 1
      netbox/netbox/tests/test_graphql.py
  12. 6 13
      netbox/project-static/yarn.lock
  13. 1 1
      netbox/templates/extras/dashboard/widgets/objectcounts.html
  14. 1 0
      netbox/tenancy/tests/test_api.py
  15. BIN
      netbox/translations/cs/LC_MESSAGES/django.mo
  16. 212 212
      netbox/translations/cs/LC_MESSAGES/django.po
  17. BIN
      netbox/translations/da/LC_MESSAGES/django.mo
  18. 212 212
      netbox/translations/da/LC_MESSAGES/django.po
  19. BIN
      netbox/translations/de/LC_MESSAGES/django.mo
  20. 212 212
      netbox/translations/de/LC_MESSAGES/django.po
  21. 246 237
      netbox/translations/en/LC_MESSAGES/django.po
  22. BIN
      netbox/translations/es/LC_MESSAGES/django.mo
  23. 212 212
      netbox/translations/es/LC_MESSAGES/django.po
  24. BIN
      netbox/translations/fr/LC_MESSAGES/django.mo
  25. 212 212
      netbox/translations/fr/LC_MESSAGES/django.po
  26. BIN
      netbox/translations/it/LC_MESSAGES/django.mo
  27. 212 212
      netbox/translations/it/LC_MESSAGES/django.po
  28. BIN
      netbox/translations/ja/LC_MESSAGES/django.mo
  29. 212 212
      netbox/translations/ja/LC_MESSAGES/django.po
  30. BIN
      netbox/translations/nl/LC_MESSAGES/django.mo
  31. 212 212
      netbox/translations/nl/LC_MESSAGES/django.po
  32. BIN
      netbox/translations/pl/LC_MESSAGES/django.mo
  33. 212 212
      netbox/translations/pl/LC_MESSAGES/django.po
  34. BIN
      netbox/translations/pt/LC_MESSAGES/django.mo
  35. 216 216
      netbox/translations/pt/LC_MESSAGES/django.po
  36. BIN
      netbox/translations/ru/LC_MESSAGES/django.mo
  37. 214 214
      netbox/translations/ru/LC_MESSAGES/django.po
  38. BIN
      netbox/translations/tr/LC_MESSAGES/django.mo
  39. 212 212
      netbox/translations/tr/LC_MESSAGES/django.po
  40. BIN
      netbox/translations/uk/LC_MESSAGES/django.mo
  41. 212 212
      netbox/translations/uk/LC_MESSAGES/django.po
  42. BIN
      netbox/translations/zh/LC_MESSAGES/django.mo
  43. 212 212
      netbox/translations/zh/LC_MESSAGES/django.po
  44. 2 0
      netbox/virtualization/tests/test_api.py
  45. 3 0
      netbox/vpn/tests/test_api.py
  46. 1 0
      netbox/wireless/tests/test_api.py
  47. 5 5
      requirements.txt

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

@@ -26,7 +26,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.0.10
+      placeholder: v4.0.11
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

+ 1 - 1
.github/ISSUE_TEMPLATE/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.0.10
+      placeholder: v4.0.11
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

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

@@ -1,5 +1,15 @@
 # NetBox v4.0
 # 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)
 ## v4.0.10 (2024-08-29)
 
 
 ### Enhancements
 ### Enhancements

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

@@ -96,6 +96,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'status': 'planned',
         'status': 'planned',
     }
     }
+    user_permissions = ('circuits.view_provider', 'circuits.view_circuittype')
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -150,6 +151,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
 class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
 class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
     model = CircuitTermination
     model = CircuitTermination
     brief_fields = ['_occupied', 'cable', 'circuit', 'description', 'display', 'id', 'term_side', 'url']
     brief_fields = ['_occupied', 'cable', 'circuit', 'description', 'display', 'id', 'term_side', 'url']
+    user_permissions = ('circuits.view_circuit', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -241,6 +243,7 @@ class CircuitGroupTest(APIViewTestCases.APIViewTestCase):
 class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
 class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
     model = ProviderAccount
     model = ProviderAccount
     brief_fields = ['account', 'description', 'display', 'id', 'name', 'url']
     brief_fields = ['account', 'description', 'display', 'id', 'name', 'url']
+    user_permissions = ('circuits.view_provider',)
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -287,6 +290,7 @@ class CircuitGroupAssignmentTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'priority': CircuitPriorityChoices.PRIORITY_INACTIVE,
         'priority': CircuitPriorityChoices.PRIORITY_INACTIVE,
     }
     }
+    user_permissions = ('circuits.view_circuit', 'circuits.view_circuitgroup')
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -355,6 +359,7 @@ class CircuitGroupAssignmentTest(APIViewTestCases.APIViewTestCase):
 class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
 class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
     model = ProviderNetwork
     model = ProviderNetwork
     brief_fields = ['description', 'display', 'id', 'name', 'url']
     brief_fields = ['description', 'display', 'id', 'name', 'url']
+    user_permissions = ('circuits.view_provider', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):

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

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

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

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

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

@@ -192,6 +192,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_site',)
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -277,6 +278,7 @@ class RackTypeTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'new description',
         'description': 'new description',
     }
     }
+    user_permissions = ('dcim.view_manufacturer',)
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -321,6 +323,7 @@ class RackTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'status': 'planned',
         'status': 'planned',
     }
     }
+    user_permissions = ('dcim.view_site', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -409,6 +412,7 @@ class RackReservationTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_rack', 'users.view_user')
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -488,6 +492,7 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'part_number': 'ABC123',
         'part_number': 'ABC123',
     }
     }
+    user_permissions = ('dcim.view_manufacturer', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -533,6 +538,7 @@ class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'part_number': 'ABC123',
         'part_number': 'ABC123',
     }
     }
+    user_permissions = ('dcim.view_manufacturer', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -704,6 +710,7 @@ class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_devicetype', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -809,6 +816,7 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_rearporttemplate', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -946,6 +954,7 @@ class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_devicetype', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -986,6 +995,7 @@ class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_devicetype', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1026,6 +1036,7 @@ class InventoryItemTemplateTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_devicetype', 'dcim.view_manufacturer',)
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1144,6 +1155,10 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'status': 'failed',
         'status': 'failed',
     }
     }
+    user_permissions = (
+        'dcim.view_site', 'dcim.view_rack', 'dcim.view_location', 'dcim.view_devicerole', 'dcim.view_devicetype',
+        'extras.view_configtemplate',
+    )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1334,6 +1349,7 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'serial': '1234ABCD',
         'serial': '1234ABCD',
     }
     }
+    user_permissions = ('dcim.view_modulebay', 'dcim.view_moduletype', 'dcim.view_device')
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1400,6 +1416,7 @@ class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = ConsoleServerPort
     peer_termination_type = ConsoleServerPort
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1442,6 +1459,7 @@ class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIView
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = ConsolePort
     peer_termination_type = ConsolePort
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1484,6 +1502,7 @@ class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = PowerOutlet
     peer_termination_type = PowerOutlet
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1523,6 +1542,7 @@ class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = PowerPort
     peer_termination_type = PowerPort
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1571,6 +1591,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = Interface
     peer_termination_type = Interface
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1705,6 +1726,7 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase):
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = Interface
     peer_termination_type = Interface
+    user_permissions = ('dcim.view_device', 'dcim.view_rearport')
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1763,6 +1785,7 @@ class RearPortTest(APIViewTestCases.APIViewTestCase):
         'description': 'New description',
         'description': 'New description',
     }
     }
     peer_termination_type = Interface
     peer_termination_type = Interface
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1804,6 +1827,7 @@ class ModuleBayTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1844,6 +1868,7 @@ class DeviceBayTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_device', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -1907,6 +1932,7 @@ class InventoryItemTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'description': 'New description',
         'description': 'New description',
     }
     }
+    user_permissions = ('dcim.view_device', 'dcim.view_manufacturer')
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -2203,6 +2229,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
 class PowerPanelTest(APIViewTestCases.APIViewTestCase):
 class PowerPanelTest(APIViewTestCases.APIViewTestCase):
     model = PowerPanel
     model = PowerPanel
     brief_fields = ['description', 'display', 'id', 'name', 'powerfeed_count', 'url']
     brief_fields = ['description', 'display', 'id', 'name', 'powerfeed_count', 'url']
+    user_permissions = ('dcim.view_site', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -2255,6 +2282,7 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
     bulk_update_data = {
     bulk_update_data = {
         'status': 'planned',
         'status': 'planned',
     }
     }
+    user_permissions = ('dcim.view_powerpanel', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     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']):
         for model in get_models_from_content_types(self.config['models']):
             permission = get_permission_for_model(model, 'view')
             permission = get_permission_for_model(model, 'view')
             if request.user.has_perm(permission):
             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')
                 qs = model.objects.restrict(request.user, 'view')
                 # Apply any specified filters
                 # Apply any specified filters
-                if filters := self.config.get('filters'):
+                if url and (filters := self.config.get('filters')):
                     params = dict_to_querydict(filters)
                     params = dict_to_querydict(filters)
                     filterset = getattr(resolve(url).func.view_class, 'filterset', None)
                     filterset = getattr(resolve(url).func.view_class, 'filterset', None)
                     qs = filterset(params, qs).qs
                     qs = filterset(params, qs).qs

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

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

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

@@ -38,7 +38,7 @@ schema = strawberry.Schema(
     query=Query,
     query=Query,
     config=StrawberryConfig(auto_camel_case=False),
     config=StrawberryConfig(auto_camel_case=False),
     extensions=[
     extensions=[
-        DjangoOptimizerExtension,
+        DjangoOptimizerExtension(prefetch_custom_queryset=True),
         MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES),
         MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES),
     ]
     ]
 )
 )

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

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

@@ -992,14 +992,7 @@ brace-expansion@^2.0.1:
   dependencies:
   dependencies:
     balanced-match "^1.0.0"
     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"
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
   integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
   integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
@@ -1575,7 +1568,7 @@ file-entry-cache@^6.0.1:
   dependencies:
   dependencies:
     flat-cache "^3.0.4"
     flat-cache "^3.0.4"
 
 
-fill-range@^7.0.1, fill-range@^7.1.1:
+fill-range@^7.1.1:
   version "7.1.1"
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
   integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
   integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
@@ -2190,11 +2183,11 @@ meros@^1.1.4:
   integrity sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==
   integrity sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==
 
 
 micromatch@^4.0.4:
 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:
   dependencies:
-    braces "^3.0.2"
+    braces "^3.0.3"
     picomatch "^2.3.1"
     picomatch "^2.3.1"
 
 
 minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
 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 %}
 {% if counts %}
   <div class="list-group list-group-flush">
   <div class="list-group list-group-flush">
     {% for model, count, url in counts %}
     {% 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">
         <div class="d-flex w-100 justify-content-between align-items-center">
           {{ model|meta:"verbose_name_plural"|bettertitle }}
           {{ model|meta:"verbose_name_plural"|bettertitle }}
           {% if count is None %}
           {% if count is None %}

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

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

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


Plik diff jest za duży
+ 212 - 212
netbox/translations/cs/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/da/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/de/LC_MESSAGES/django.po


Plik diff jest za duży
+ 246 - 237
netbox/translations/en/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/es/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/fr/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/it/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/ja/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/nl/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/pl/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 216 - 216
netbox/translations/pt/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 214 - 214
netbox/translations/ru/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/tr/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 212 - 212
netbox/translations/uk/LC_MESSAGES/django.po


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


Plik diff jest za duży
+ 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',
         'description': 'New description',
     }
     }
     graphql_base_name = 'vm_interface'
     graphql_base_name = 'vm_interface'
+    user_permissions = ('virtualization.view_virtualmachine', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
@@ -342,6 +343,7 @@ class VirtualDiskTest(APIViewTestCases.APIViewTestCase):
         'size': 888,
         'size': 888,
     }
     }
     graphql_base_name = 'virtual_disk'
     graphql_base_name = 'virtual_disk'
+    user_permissions = ('virtualization.view_virtualmachine', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):

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

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

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

@@ -116,6 +116,7 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
         'distance': 100,
         'distance': 100,
         'distance_unit': 'm',
         'distance_unit': 'm',
     }
     }
+    user_permissions = ('dcim.view_interface', )
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     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-cors-headers==4.4.0
 django-debug-toolbar==4.4.6
 django-debug-toolbar==4.4.6
 django-filter==24.3
 django-filter==24.3
@@ -8,7 +8,7 @@ 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.10.0
+django-rich==1.11.0
 django-rq==2.10.2
 django-rq==2.10.2
 django-taggit==6.0.0
 django-taggit==6.0.0
 django-tables2==2.7.0
 django-tables2==2.7.0
@@ -20,8 +20,8 @@ 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.33
-mkdocstrings[python-legacy]==0.25.2
+mkdocs-material==9.5.34
+mkdocstrings[python-legacy]==0.26.0
 netaddr==1.3.0
 netaddr==1.3.0
 nh3==0.2.18
 nh3==0.2.18
 Pillow==10.4.0
 Pillow==10.4.0
@@ -30,7 +30,7 @@ PyYAML==6.0.2
 requests==2.32.3
 requests==2.32.3
 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.237.3
+strawberry-graphql==0.239.2
 strawberry-graphql-django==0.47.1
 strawberry-graphql-django==0.47.1
 svgwrite==1.4.3
 svgwrite==1.4.3
 tablib==3.6.1
 tablib==3.6.1

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików