Explorar el Código

Merge branch 'develop' into fix/generic_prefetch_4.2

Andrey Tikhonov hace 11 meses
padre
commit
d226af420b

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

@@ -34,10 +34,9 @@ body:
       label: Python Version
       label: Python Version
       description: What version of Python are you currently running?
       description: What version of Python are you currently running?
       options:
       options:
-        - "3.8"
-        - "3.9"
         - "3.10"
         - "3.10"
         - "3.11"
         - "3.11"
+        - "3.12"
     validations:
     validations:
       required: true
       required: true
   - type: textarea
   - type: textarea

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

@@ -1,5 +1,9 @@
 # NetBox v4.0
 # NetBox v4.0
 
 
+## v4.0.1 (FUTURE)
+
+---
+
 ## v4.0.0 (2024-05-06)
 ## v4.0.0 (2024-05-06)
 
 
 !!! tip "Plugin Maintainers"
 !!! tip "Plugin Maintainers"

+ 1 - 1
netbox/circuits/api/serializers_/circuits.py

@@ -48,7 +48,7 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
 class CircuitSerializer(NetBoxModelSerializer):
 class CircuitSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
     provider = ProviderSerializer(nested=True)
     provider = ProviderSerializer(nested=True)
-    provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True)
+    provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
     status = ChoiceField(choices=CircuitStatusChoices, required=False)
     status = ChoiceField(choices=CircuitStatusChoices, required=False)
     type = CircuitTypeSerializer(nested=True)
     type = CircuitTypeSerializer(nested=True)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True)

+ 1 - 0
netbox/circuits/api/serializers_/providers.py

@@ -45,6 +45,7 @@ class ProviderSerializer(NetBoxModelSerializer):
 class ProviderAccountSerializer(NetBoxModelSerializer):
 class ProviderAccountSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')
     provider = ProviderSerializer(nested=True)
     provider = ProviderSerializer(nested=True)
+    name = serializers.CharField(allow_blank=True, max_length=100, required=False, default='')
 
 
     class Meta:
     class Meta:
         model = ProviderAccount
         model = ProviderAccount

+ 2 - 2
netbox/circuits/tests/test_api.py

@@ -141,7 +141,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
             {
             {
                 'cid': 'Circuit 6',
                 'cid': 'Circuit 6',
                 'provider': providers[1].pk,
                 'provider': providers[1].pk,
-                'provider_account': provider_accounts[1].pk,
+                # Omit provider account to test uniqueness constraint
                 'type': circuit_types[1].pk,
                 'type': circuit_types[1].pk,
             },
             },
         ]
         ]
@@ -237,7 +237,7 @@ class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
                 'account': '5678',
                 'account': '5678',
             },
             },
             {
             {
-                'name': 'Provider Account 6',
+                # Omit name to test uniqueness constraint
                 'provider': providers[0].pk,
                 'provider': providers[0].pk,
                 'account': '6789',
                 'account': '6789',
             },
             },

+ 1 - 0
netbox/dcim/api/serializers_/devices.py

@@ -122,6 +122,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
 class VirtualDeviceContextSerializer(NetBoxModelSerializer):
 class VirtualDeviceContextSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
     device = DeviceSerializer(nested=True)
     device = DeviceSerializer(nested=True)
+    identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
     primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
     primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
     primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
     primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)

+ 2 - 2
netbox/dcim/api/serializers_/sites.py

@@ -51,7 +51,7 @@ class SiteSerializer(NetBoxModelSerializer):
     status = ChoiceField(choices=SiteStatusChoices, required=False)
     status = ChoiceField(choices=SiteStatusChoices, required=False)
     region = RegionSerializer(nested=True, required=False, allow_null=True)
     region = RegionSerializer(nested=True, required=False, allow_null=True)
     group = SiteGroupSerializer(nested=True, required=False, allow_null=True)
     group = SiteGroupSerializer(nested=True, required=False, allow_null=True)
-    tenant = TenantSerializer(required=False, allow_null=True)
+    tenant = TenantSerializer(nested=True, required=False, allow_null=True)
     time_zone = TimeZoneSerializerField(required=False, allow_null=True)
     time_zone = TimeZoneSerializerField(required=False, allow_null=True)
     asns = SerializedPKRelatedField(
     asns = SerializedPKRelatedField(
         queryset=ASN.objects.all(),
         queryset=ASN.objects.all(),
@@ -83,7 +83,7 @@ class SiteSerializer(NetBoxModelSerializer):
 class LocationSerializer(NestedGroupModelSerializer):
 class LocationSerializer(NestedGroupModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
     site = SiteSerializer(nested=True)
     site = SiteSerializer(nested=True)
-    parent = NestedLocationSerializer(required=False, allow_null=True)
+    parent = NestedLocationSerializer(required=False, allow_null=True, default=None)
     status = ChoiceField(choices=LocationStatusChoices, required=False)
     status = ChoiceField(choices=LocationStatusChoices, required=False)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True)
     rack_count = serializers.IntegerField(read_only=True)
     rack_count = serializers.IntegerField(read_only=True)

+ 5 - 2
netbox/dcim/tests/test_api.py

@@ -10,6 +10,7 @@ from dcim.models import *
 from extras.models import ConfigTemplate
 from extras.models import ConfigTemplate
 from ipam.models import ASN, RIR, VLAN, VRF
 from ipam.models import ASN, RIR, VLAN, VRF
 from netbox.api.serializers import GenericObjectSerializer
 from netbox.api.serializers import GenericObjectSerializer
+from tenancy.models import Tenant
 from utilities.testing import APITestCase, APIViewTestCases, create_test_device
 from utilities.testing import APITestCase, APIViewTestCases, create_test_device
 from virtualization.models import Cluster, ClusterType
 from virtualization.models import Cluster, ClusterType
 from wireless.choices import WirelessChannelChoices
 from wireless.choices import WirelessChannelChoices
@@ -152,6 +153,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
         Site.objects.bulk_create(sites)
         Site.objects.bulk_create(sites)
 
 
         rir = RIR.objects.create(name='RFC 6996', is_private=True)
         rir = RIR.objects.create(name='RFC 6996', is_private=True)
+        tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
 
 
         asns = [
         asns = [
             ASN(asn=65000 + i, rir=rir) for i in range(8)
             ASN(asn=65000 + i, rir=rir) for i in range(8)
@@ -166,6 +168,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
                 'group': groups[1].pk,
                 'group': groups[1].pk,
                 'status': SiteStatusChoices.STATUS_ACTIVE,
                 'status': SiteStatusChoices.STATUS_ACTIVE,
                 'asns': [asns[0].pk, asns[1].pk],
                 'asns': [asns[0].pk, asns[1].pk],
+                'tenant': tenant.pk,
             },
             },
             {
             {
                 'name': 'Site 5',
                 'name': 'Site 5',
@@ -230,7 +233,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase):
                 'name': 'Test Location 6',
                 'name': 'Test Location 6',
                 'slug': 'test-location-6',
                 'slug': 'test-location-6',
                 'site': sites[1].pk,
                 'site': sites[1].pk,
-                'parent': parent_locations[1].pk,
+                # Omit parent to test uniqueness constraint
                 'status': LocationStatusChoices.STATUS_PLANNED,
                 'status': LocationStatusChoices.STATUS_PLANNED,
             },
             },
         ]
         ]
@@ -2307,6 +2310,6 @@ class VirtualDeviceContextTest(APIViewTestCases.APIViewTestCase):
                 'device': devices[1].pk,
                 'device': devices[1].pk,
                 'status': 'active',
                 'status': 'active',
                 'name': 'VDC 3',
                 'name': 'VDC 3',
-                'identifier': 3,
+                # Omit identifier to test uniqueness constraint
             },
             },
         ]
         ]

+ 1 - 0
netbox/ipam/views.py

@@ -781,6 +781,7 @@ class IPAddressView(generic.ObjectView):
 class IPAddressEditView(generic.ObjectEditView):
 class IPAddressEditView(generic.ObjectEditView):
     queryset = IPAddress.objects.all()
     queryset = IPAddress.objects.all()
     form = forms.IPAddressForm
     form = forms.IPAddressForm
+    template_name = 'ipam/ipaddress_edit.html'
 
 
     def alter_object(self, obj, request, url_args, url_kwargs):
     def alter_object(self, obj, request, url_args, url_kwargs):
 
 

+ 1 - 0
netbox/netbox/navigation/__init__.py

@@ -32,6 +32,7 @@ class MenuItem:
     link: str
     link: str
     link_text: str
     link_text: str
     permissions: Optional[Sequence[str]] = ()
     permissions: Optional[Sequence[str]] = ()
+    auth_required: Optional[bool] = False
     staff_only: Optional[bool] = False
     staff_only: Optional[bool] = False
     buttons: Optional[Sequence[MenuItemButton]] = ()
     buttons: Optional[Sequence[MenuItemButton]] = ()
 
 

+ 9 - 2
netbox/netbox/navigation/menu.py

@@ -371,6 +371,7 @@ ADMIN_MENU = Menu(
                 MenuItem(
                 MenuItem(
                     link=f'users:user_list',
                     link=f'users:user_list',
                     link_text=_('Users'),
                     link_text=_('Users'),
+                    auth_required=True,
                     permissions=[f'auth.view_user'],
                     permissions=[f'auth.view_user'],
                     buttons=(
                     buttons=(
                         MenuItemButton(
                         MenuItemButton(
@@ -390,6 +391,7 @@ ADMIN_MENU = Menu(
                 MenuItem(
                 MenuItem(
                     link=f'users:group_list',
                     link=f'users:group_list',
                     link_text=_('Groups'),
                     link_text=_('Groups'),
+                    auth_required=True,
                     permissions=[f'auth.view_group'],
                     permissions=[f'auth.view_group'],
                     buttons=(
                     buttons=(
                         MenuItemButton(
                         MenuItemButton(
@@ -409,12 +411,14 @@ ADMIN_MENU = Menu(
                 MenuItem(
                 MenuItem(
                     link=f'users:token_list',
                     link=f'users:token_list',
                     link_text=_('API Tokens'),
                     link_text=_('API Tokens'),
+                    auth_required=True,
                     permissions=[f'users.view_token'],
                     permissions=[f'users.view_token'],
                     buttons=get_model_buttons('users', 'token')
                     buttons=get_model_buttons('users', 'token')
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link=f'users:objectpermission_list',
                     link=f'users:objectpermission_list',
                     link_text=_('Permissions'),
                     link_text=_('Permissions'),
+                    auth_required=True,
                     permissions=[f'users.view_objectpermission'],
                     permissions=[f'users.view_objectpermission'],
                     buttons=get_model_buttons('users', 'objectpermission', actions=['add'])
                     buttons=get_model_buttons('users', 'objectpermission', actions=['add'])
                 ),
                 ),
@@ -425,16 +429,19 @@ ADMIN_MENU = Menu(
             items=(
             items=(
                 MenuItem(
                 MenuItem(
                     link='core:system',
                     link='core:system',
-                    link_text=_('System')
+                    link_text=_('System'),
+                    auth_required=True
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link='core:configrevision_list',
                     link='core:configrevision_list',
                     link_text=_('Configuration History'),
                     link_text=_('Configuration History'),
+                    auth_required=True,
                     permissions=['core.view_configrevision']
                     permissions=['core.view_configrevision']
                 ),
                 ),
                 MenuItem(
                 MenuItem(
                     link='core:background_queue_list',
                     link='core:background_queue_list',
-                    link_text=_('Background Tasks')
+                    link_text=_('Background Tasks'),
+                    auth_required=True
                 ),
                 ),
             ),
             ),
         ),
         ),

+ 1 - 2
netbox/netbox/settings.py

@@ -25,7 +25,7 @@ from utilities.string import trailing_slash
 # Environment setup
 # Environment setup
 #
 #
 
 
-VERSION = '4.0.0'
+VERSION = '4.0.1-dev'
 HOSTNAME = platform.node()
 HOSTNAME = platform.node()
 # Set the base directory two levels up
 # Set the base directory two levels up
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -522,7 +522,6 @@ if SENTRY_ENABLED:
     sentry_sdk.init(
     sentry_sdk.init(
         dsn=SENTRY_DSN,
         dsn=SENTRY_DSN,
         release=VERSION,
         release=VERSION,
-        integrations=[sentry_sdk.integrations.django.DjangoIntegration()],
         sample_rate=SENTRY_SAMPLE_RATE,
         sample_rate=SENTRY_SAMPLE_RATE,
         traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
         traces_sample_rate=SENTRY_TRACES_SAMPLE_RATE,
         send_default_pii=True,
         send_default_pii=True,

+ 1 - 1
netbox/templates/base/base.html

@@ -20,7 +20,7 @@
     {# Initialize color mode #}
     {# Initialize color mode #}
     <script
     <script
       type="text/javascript"
       type="text/javascript"
-      src="{% static 'setmode.js' %}"
+      src="{% static 'setmode.js' %}?v={{ settings.VERSION }}"
       onerror="window.location='{% url 'media_failure' %}?filename=setmode.js'">
       onerror="window.location='{% url 'media_failure' %}?filename=setmode.js'">
     </script>
     </script>
     <script type="text/javascript">
     <script type="text/javascript">

+ 12 - 21
netbox/templates/ipam/inc/ipaddress_edit_header.html

@@ -3,30 +3,21 @@
 
 
 <ul class="nav nav-tabs">
 <ul class="nav nav-tabs">
   <li class="nav-item">
   <li class="nav-item">
-      <a
-          class="nav-link {% if active_tab == 'add' %}active{% endif %}"
-          href="{% url 'ipam:ipaddress_add' %}{% querystring request %}"
-      >
-          {% if obj.pk %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}
-      </a>
+    <a href="{% url 'ipam:ipaddress_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
+      {% if object.pk %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}
+    </a>
   </li>
   </li>
   {% if 'interface' in request.GET or 'vminterface' in request.GET %}
   {% if 'interface' in request.GET or 'vminterface' in request.GET %}
-  <li class="nav-item">
-      <a
-          class="nav-link {% if active_tab == 'assign' %}active{% endif %}"
-          href="{% url 'ipam:ipaddress_assign' %}{% querystring request %}"
-      >
-          {% trans "Assign IP" %}
+    <li class="nav-item">
+      <a href="{% url 'ipam:ipaddress_assign' %}{% querystring request %}" class="nav-link {% if active_tab == 'assign' %}active{% endif %}">
+        {% trans "Assign IP" %}
       </a>
       </a>
-  </li>
-  {% else %}
-  <li class="nav-item">
-      <a
-          class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}"
-          href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring request %}"
-      >
-          {% trans "Bulk Create" %}
+    </li>
+  {% elif not object.pk %}
+    <li class="nav-item">
+      <a href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}">
+        {% trans "Bulk Create" %}
       </a>
       </a>
-  </li>
+    </li>
   {% endif %}
   {% endif %}
 </ul>
 </ul>

+ 26 - 30
netbox/templates/ipam/ipaddress_assign.html

@@ -12,37 +12,33 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block form %}
 {% block form %}
-    <form action="{% querystring request %}" method="post" class="form form-horizontal">
-        {% csrf_token %}
-        {% for field in form.hidden_fields %}
-            {{ field }}
-        {% endfor %}
-        <div class="row mb-3">
-            <div class="col col-md-8 offset-md-2">
-                <div class="field-group">
-                    <h6>{% trans "Select IP Address" %}</h6>
-                    {% render_field form.vrf_id %}
-                    {% render_field form.q %}
-                </div>
-            </div>
+  <form action="{% querystring request %}" method="post" class="form form-horizontal">
+    {% csrf_token %}
+    {% for field in form.hidden_fields %}
+      {{ field }}
+    {% endfor %}
+    <div class="field-group my-5">
+      <div class="row">
+        <h5 class="col-9 offset-3">{% trans "Select IP Address" %}</h5>
+      </div>
+      {% render_field form.vrf_id %}
+      {% render_field form.q %}
+    </div>
+    <div class="text-end my-3">
+      <a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
+      <button type="submit" class="btn btn-primary">{% trans "Search" %}</button>
+    </div>
+  </form>
+  {% if table %}
+    <div class="row mb-3">
+      <div class="col col-md-12">
+        <h3>{% trans "Search Results" %}</h3>
+        <div class="table-responsive">
+          {% render_table table 'inc/table.html' %}
         </div>
         </div>
-        <div class="row mb-3">
-            <div class="col col-md-8 offset-md-2 text-end">
-                <a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
-                <button type="submit" class="btn btn-primary">{% trans "Search" %}</button>
-            </div>
-        </div>
-    </form>
-    {% if table %}
-        <div class="row mb-3">
-            <div class="col col-md-12">
-                <h3>{% trans "Search Results" %}</h3>
-                <div class="table-responsive">
-                  {% render_table table 'inc/table.html' %}
-                </div>
-            </div>
-        </div>
-    {% endif %}
+      </div>
+    </div>
+  {% endif %}
 {% endblock form %}
 {% endblock form %}
 
 
 {% block buttons %}
 {% block buttons %}

+ 5 - 0
netbox/templates/ipam/ipaddress_edit.html

@@ -0,0 +1,5 @@
+{% extends 'generic/object_edit.html' %}
+
+{% block tabs %}
+  {% include 'ipam/inc/ipaddress_edit_header.html' with active_tab='add' %}
+{% endblock %}

+ 1 - 1
netbox/tenancy/api/serializers_/tenants.py

@@ -27,7 +27,7 @@ class TenantGroupSerializer(NestedGroupModelSerializer):
 
 
 class TenantSerializer(NetBoxModelSerializer):
 class TenantSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
     url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenant-detail')
-    group = TenantGroupSerializer(nested=True, required=False, allow_null=True)
+    group = TenantGroupSerializer(nested=True, required=False, allow_null=True, default=None)
 
 
     # Related object counts
     # Related object counts
     circuit_count = RelatedObjectCountField('circuits')
     circuit_count = RelatedObjectCountField('circuits')

+ 2 - 0
netbox/utilities/templatetags/navigation.py

@@ -26,6 +26,8 @@ def nav(context):
         for group in menu.groups:
         for group in menu.groups:
             items = []
             items = []
             for item in group.items:
             for item in group.items:
+                if getattr(item, 'auth_required', False) and not user.is_authenticated:
+                    continue
                 if not user.has_perms(item.permissions):
                 if not user.has_perms(item.permissions):
                     continue
                     continue
                 if item.staff_only and not user.is_staff:
                 if item.staff_only and not user.is_staff:

+ 4 - 5
netbox/virtualization/api/serializers_/virtualmachines.py

@@ -31,11 +31,11 @@ __all__ = (
 class VirtualMachineSerializer(NetBoxModelSerializer):
 class VirtualMachineSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
     status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
     status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
-    site = SiteSerializer(nested=True, required=False, allow_null=True)
-    cluster = ClusterSerializer(nested=True, required=False, allow_null=True)
-    device = DeviceSerializer(nested=True, required=False, allow_null=True)
+    site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
+    cluster = ClusterSerializer(nested=True, required=False, allow_null=True, default=None)
+    device = DeviceSerializer(nested=True, required=False, allow_null=True, default=None)
     role = DeviceRoleSerializer(nested=True, required=False, allow_null=True)
     role = DeviceRoleSerializer(nested=True, required=False, allow_null=True)
-    tenant = TenantSerializer(nested=True, required=False, allow_null=True)
+    tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
     platform = PlatformSerializer(nested=True, required=False, allow_null=True)
     platform = PlatformSerializer(nested=True, required=False, allow_null=True)
     primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
     primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
     primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
     primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
@@ -55,7 +55,6 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
             'interface_count', 'virtual_disk_count',
             'interface_count', 'virtual_disk_count',
         ]
         ]
         brief_fields = ('id', 'url', 'display', 'name', 'description')
         brief_fields = ('id', 'url', 'display', 'name', 'description')
-        validators = []
 
 
 
 
 class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
 class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):

+ 1 - 1
requirements.txt

@@ -1,4 +1,4 @@
-Django==5.0.5
+Django==5.0.6
 django-cors-headers==4.3.1
 django-cors-headers==4.3.1
 django-debug-toolbar==4.3.0
 django-debug-toolbar==4.3.0
 django-filter==24.2
 django-filter==24.2