From 3675ad2539400038af6797140ddca3679b8bca30 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 16 Dec 2022 17:18:06 -0500 Subject: [PATCH 01/21] PRVB --- docs/release-notes/version-3.4.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 82d0b151c..b511aee98 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -1,5 +1,9 @@ # NetBox v3.4 +## v3.4.2 (FUTURE) + +--- + ## v3.4.1 (2022-12-16) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index bc1b713b1..cc0a3c016 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -24,7 +24,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.4.1' +VERSION = '3.4.2-dev' # Hostname HOSTNAME = platform.node() From db5c2a379eb9383ac417e6c2686c0fd889c13675 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 22 Dec 2022 09:14:57 -0500 Subject: [PATCH 02/21] Fixes #11232: Enable partial & regex matching for non-string types in global search --- docs/release-notes/version-3.4.md | 4 ++++ netbox/netbox/search/backends.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index b511aee98..8375e4094 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -2,6 +2,10 @@ ## v3.4.2 (FUTURE) +### Bug Fixes + +* [#11232](https://github.com/netbox-community/netbox/issues/11232) - Enable partial & regular expression matching for non-string types in global search + --- ## v3.4.1 (2022-12-16) diff --git a/netbox/netbox/search/backends.py b/netbox/netbox/search/backends.py index dfc251aa9..d659a7abb 100644 --- a/netbox/netbox/search/backends.py +++ b/netbox/netbox/search/backends.py @@ -99,8 +99,8 @@ class CachedValueSearchBackend(SearchBackend): params = { f'value__{lookup}': value } - if lookup != LookupTypes.EXACT: - # Partial matches are valid only on string values + if lookup in (LookupTypes.STARTSWITH, LookupTypes.ENDSWITH): + # Partial string matches are valid only on string values params['type'] = FieldTypes.STRING if object_types: params['object_type__in'] = object_types From b35b33e7986a72a9cbdadfbc09dbfd72a64518a2 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Thu, 22 Dec 2022 12:28:30 +0100 Subject: [PATCH 03/21] Use the start time to calculate duration of jobs instead of created time --- netbox/extras/models/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 6a60458e2..699baa11b 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -651,7 +651,12 @@ class JobResult(models.Model): if not self.completed: return None - duration = self.completed - self.created + start_time = self.started or self.created + + if not start_time: + return None + + duration = self.completed - start_time minutes, seconds = divmod(duration.total_seconds(), 60) return f"{int(minutes)} minutes, {seconds:.2f} seconds" From bfab3a26bc0c8a4f28eebdc6cae943e16ee1f292 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Wed, 21 Dec 2022 13:10:43 +0100 Subject: [PATCH 04/21] Add component import to InventoryItem bulk import --- netbox/dcim/forms/bulk_import.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 940d05127..bdbaf9f18 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -885,12 +885,22 @@ class InventoryItemImportForm(NetBoxModelImportForm): required=False, help_text=_('Parent inventory item') ) + component_type = CSVContentTypeField( + queryset=ContentType.objects.all(), + limit_choices_to=MODULAR_COMPONENT_MODELS, + required=False, + help_text=_('Component Type') + ) + component_name = forms.CharField( + required=False, + help_text=_('Component Name') + ) class Meta: model = InventoryItem fields = ( 'device', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', - 'description', 'tags' + 'description', 'tags', 'component_type', 'component_name', ) def __init__(self, *args, **kwargs): @@ -908,6 +918,24 @@ class InventoryItemImportForm(NetBoxModelImportForm): else: self.fields['parent'].queryset = InventoryItem.objects.none() + def clean_component_name(self): + content_type = self.cleaned_data.get('component_type') + component_name = self.cleaned_data.get('component_name') + device = self.cleaned_data.get("device") + + if not device and hasattr(self, 'instance'): + device = self.instance.device + + if not all([device, content_type, component_name]): + return None + + model = content_type.model_class() + try: + component = model.objects.get(device=device, name=component_name) + self.instance.component = component + except ObjectDoesNotExist: + raise forms.ValidationError(f"Component not found: {device} - {component_name}") + # # Device component roles From 92da2fe082758cd057fba0f9194cce6914f5d89b Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Mon, 19 Dec 2022 13:22:53 +0100 Subject: [PATCH 05/21] Add device name as part of module search for the q filter --- netbox/dcim/filtersets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 1ebcb2e34..2360fc4aa 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1098,6 +1098,7 @@ class ModuleFilterSet(NetBoxModelFilterSet): if not value.strip(): return queryset return queryset.filter( + Q(device__name__icontains=value.strip()) | Q(serial__icontains=value.strip()) | Q(asset_tag__icontains=value.strip()) | Q(comments__icontains=value) From 98b3fc03b8d18b1767e112dee77c7f2805971489 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 22 Dec 2022 10:13:47 -0500 Subject: [PATCH 06/21] Changelog for #9285, #10700, #11290 --- docs/release-notes/version-3.4.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 8375e4094..f0134aa83 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -2,8 +2,14 @@ ## v3.4.2 (FUTURE) +### Enhancements + +* [#9285](https://github.com/netbox-community/netbox/issues/9285) - Enable specifying assigned component during bulk import of inventory items +* [#10700](https://github.com/netbox-community/netbox/issues/10700) - Match device name when using modules quick search + ### Bug Fixes +* [#11290](https://github.com/netbox-community/netbox/issues/11290) - Correct reporting of scheduled job duration * [#11232](https://github.com/netbox-community/netbox/issues/11232) - Enable partial & regular expression matching for non-string types in global search --- From c7108bb3f7c77d7b1d777ae1cf0ac103dcd3bd53 Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Tue, 27 Dec 2022 16:15:28 +0100 Subject: [PATCH 07/21] Fixes #11280 - Fix exporting interfaces and FHRP group rows with multiple IP's assigned (#11285) undefined --- netbox/dcim/tables/devices.py | 3 +++ netbox/ipam/tables/fhrp.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 7b8ea1ed3..7a2ea50ba 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -506,6 +506,9 @@ class BaseInterfaceTable(NetBoxTable): verbose_name='Tagged VLANs' ) + def value_ip_addresses(self, value): + return ",".join([str(obj.address) for obj in value.all()]) + class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable): device = tables.Column( diff --git a/netbox/ipam/tables/fhrp.py b/netbox/ipam/tables/fhrp.py index 89aa16e65..7d7b03bb8 100644 --- a/netbox/ipam/tables/fhrp.py +++ b/netbox/ipam/tables/fhrp.py @@ -33,6 +33,9 @@ class FHRPGroupTable(NetBoxTable): url_name='ipam:fhrpgroup_list' ) + def value_ip_addresses(self, value): + return ",".join([str(obj.address) for obj in value.all()]) + class Meta(NetBoxTable.Meta): model = FHRPGroup fields = ( From 735fa4aa3118da66a794c314d9d1668617734cc2 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Mon, 19 Dec 2022 14:57:35 +0100 Subject: [PATCH 08/21] Add summed resource card to cluster view --- netbox/templates/virtualization/cluster.html | 31 ++++++++++++++++++++ netbox/virtualization/views.py | 5 +++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 510c5a48e..5f34a82c5 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -55,6 +55,37 @@ {% plugin_left_page object %}
+
+
Allocated Resources
+
+ + + + + + + + + + + + + +
Virtual CPUs{{ vcpus_sum|placeholder }}
Memory + {% if memory_sum %} + {{ memory_sum|humanize_megabytes }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
Disk Space + {% if disk_sum %} + {{ disk_sum }} GB + {% else %} + {{ ''|placeholder }} + {% endif %} +
+
+
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} {% include 'inc/panels/contacts.html' %} diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index b97b966b4..af130fcce 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -1,6 +1,6 @@ from django.contrib import messages from django.db import transaction -from django.db.models import Prefetch +from django.db.models import Prefetch, Sum from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext as _ @@ -169,6 +169,9 @@ class ClusterListView(generic.ObjectListView): class ClusterView(generic.ObjectView): queryset = Cluster.objects.all() + def get_extra_context(self, request, instance): + return instance.virtual_machines.aggregate(vcpus_sum=Sum('vcpus'), memory_sum=Sum('memory'), disk_sum=Sum('disk')) + @register_model_view(Cluster, 'virtualmachines', path='virtual-machines') class ClusterVirtualMachinesView(generic.ObjectChildrenView): From 98f57f2dbaf0f3e4d686c9ae9377d2ae6c5723ac Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 27 Dec 2022 11:12:25 -0800 Subject: [PATCH 09/21] 11297 have custom field form display content-type instead of model --- netbox/extras/forms/model_forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index f9b145803..a21cf21e2 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -32,7 +32,6 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm): content_types = ContentTypeMultipleChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('custom_fields'), - label=_('Model(s)') ) object_type = ContentTypeChoiceField( queryset=ContentType.objects.all(), From b6cd0991172f61a1a0680e39cca2056f32c83d5c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 27 Dec 2022 16:07:02 -0500 Subject: [PATCH 10/21] Changelog for #11121, #11280 --- docs/release-notes/version-3.4.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index f0134aa83..1d29c14b5 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -6,9 +6,11 @@ * [#9285](https://github.com/netbox-community/netbox/issues/9285) - Enable specifying assigned component during bulk import of inventory items * [#10700](https://github.com/netbox-community/netbox/issues/10700) - Match device name when using modules quick search +* [#11121](https://github.com/netbox-community/netbox/issues/11121) - Add VM resource totals to cluster view ### Bug Fixes +* [#11280](https://github.com/netbox-community/netbox/issues/11280) - Fix errant newlines when exporting interfaces with multiple IP addresses assigned * [#11290](https://github.com/netbox-community/netbox/issues/11290) - Correct reporting of scheduled job duration * [#11232](https://github.com/netbox-community/netbox/issues/11232) - Enable partial & regular expression matching for non-string types in global search From ae440c9edf62da125eb2dccce36b9c440ef0e99a Mon Sep 17 00:00:00 2001 From: Alef Burzmali Date: Wed, 28 Dec 2022 19:49:06 +0100 Subject: [PATCH 11/21] Fixes #11223 - Accept app_label for reindex --- netbox/extras/management/commands/reindex.py | 29 ++++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/netbox/extras/management/commands/reindex.py b/netbox/extras/management/commands/reindex.py index f519688f8..b601a1ac1 100644 --- a/netbox/extras/management/commands/reindex.py +++ b/netbox/extras/management/commands/reindex.py @@ -27,17 +27,28 @@ class Command(BaseCommand): # Return only indexers for the specified models else: for label in model_names: - try: - app_label, model_name = label.lower().split('.') - except ValueError: + labels = label.lower().split('.') + + # Label specifies an exact model + if len(labels) == 2: + app_label, model_name = labels + try: + idx = registry['search'][f'{app_label}.{model_name}'] + indexers[idx.model] = idx + except KeyError: + raise CommandError(f"No indexer registered for {label}") + + # Label specifies all the models of an app + elif len(labels) == 1: + app_label = labels[0] + '.' + for indexer_label, idx in registry['search'].items(): + if indexer_label.startswith(app_label): + indexers[idx.model] = idx + + else: raise CommandError( - f"Invalid model: {label}. Model names must be in the format .." + f"Invalid model: {label}. Model names must be in the format or .." ) - try: - idx = registry['search'][f'{app_label}.{model_name}'] - indexers[idx.model] = idx - except KeyError: - raise CommandError(f"No indexer registered for {label}") return indexers From b7cdbd3d41397c91cf7a6f3ca2c5e90e30b56c96 Mon Sep 17 00:00:00 2001 From: Alef Burzmali Date: Wed, 28 Dec 2022 21:06:11 +0100 Subject: [PATCH 12/21] Fixes #11248 - Reindex only NetBox apps --- netbox/extras/migrations/0083_search.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/netbox/extras/migrations/0083_search.py b/netbox/extras/migrations/0083_search.py index 8f67717bb..0c53de638 100644 --- a/netbox/extras/migrations/0083_search.py +++ b/netbox/extras/migrations/0083_search.py @@ -10,7 +10,16 @@ from django.db import migrations, models def reindex(apps, schema_editor): # Build the search index (except during tests) if 'test' not in sys.argv: - management.call_command('reindex') + management.call_command( + 'reindex', + 'circuits', + 'dcim', + 'extras', + 'ipam', + 'tenancy', + 'virtualization', + 'wireless', + ) class Migration(migrations.Migration): From ccb2966c4ca641aa6eeca658a902319de4c4f123 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 28 Dec 2022 22:54:33 +0100 Subject: [PATCH 13/21] Fixes #11244: Elevations: Filter badge missing (#11321) * Added filter badge in rack elevation * Tweak template context Co-authored-by: Jeremy Stretch --- netbox/dcim/views.py | 1 + netbox/templates/dcim/rack_elevation_list.html | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 06a486534..870cbcc18 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -691,6 +691,7 @@ class RackElevationListView(generic.ObjectListView): 'sort_choices': ORDERING_CHOICES, 'rack_face': rack_face, 'filter_form': forms.RackElevationFilterForm(request.GET), + 'model': self.queryset.model, }) diff --git a/netbox/templates/dcim/rack_elevation_list.html b/netbox/templates/dcim/rack_elevation_list.html index ef22bd9b8..c9d9a248a 100644 --- a/netbox/templates/dcim/rack_elevation_list.html +++ b/netbox/templates/dcim/rack_elevation_list.html @@ -35,6 +35,10 @@ {% block content-wrapper %}
+ {% if filter_form %} + {% applied_filters model filter_form request.GET %} + {% endif %} + {# Rack elevations #}
{% if page %} From d41716880536901a6f70cc664e4ae31a640c82b2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 28 Dec 2022 16:58:04 -0500 Subject: [PATCH 14/21] Changelog for #11223, #11244, #11248 --- docs/release-notes/version-3.4.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 1d29c14b5..fb2eec5dd 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -7,6 +7,9 @@ * [#9285](https://github.com/netbox-community/netbox/issues/9285) - Enable specifying assigned component during bulk import of inventory items * [#10700](https://github.com/netbox-community/netbox/issues/10700) - Match device name when using modules quick search * [#11121](https://github.com/netbox-community/netbox/issues/11121) - Add VM resource totals to cluster view +* [#11223](https://github.com/netbox-community/netbox/issues/11223) - `reindex` management command should accept app label without model name +* [#11244](https://github.com/netbox-community/netbox/issues/11244) - Add controls for saved filters to rack elevations list +* [#11248](https://github.com/netbox-community/netbox/issues/11248) - Fix database migration when plugin with search indexer is enabled ### Bug Fixes From 08a419ec7ae24df64c80a7c10d99ae2a6d621e80 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Thu, 29 Dec 2022 06:04:35 -0800 Subject: [PATCH 15/21] 11271 flag to disable localization (#11323) * 11271 flag to disable localization * 11271 change to remove middleware * 11271 update docs for new var * Update docs/configuration/system.md Co-authored-by: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Co-authored-by: Jeremy Stretch Co-authored-by: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> --- docs/configuration/system.md | 8 ++++++++ netbox/netbox/configuration_example.py | 3 +++ netbox/netbox/settings.py | 11 +++++++++++ 3 files changed, 22 insertions(+) diff --git a/docs/configuration/system.md b/docs/configuration/system.md index 5a7c8bebd..7061274f1 100644 --- a/docs/configuration/system.md +++ b/docs/configuration/system.md @@ -65,6 +65,14 @@ Email is sent from NetBox only for critical events or if configured for [logging --- +## ENABLE_LOCALIZATION + +Default: False + +Determines if localization features are enabled or not. This should only be enabled for development or testing purposes as netbox is not yet fully localized. Turning this on will localize numeric and date formats (overriding what is set for DATE_FORMAT) based on the browser locale as well as translate certain strings from third party modules. + +--- + ## HTTP_PROXIES Default: None diff --git a/netbox/netbox/configuration_example.py b/netbox/netbox/configuration_example.py index f298b35fe..9d9651462 100644 --- a/netbox/netbox/configuration_example.py +++ b/netbox/netbox/configuration_example.py @@ -222,6 +222,9 @@ SESSION_COOKIE_NAME = 'sessionid' # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. SESSION_FILE_PATH = None +# Localization +ENABLE_LOCALIZATION = False + # Time zone (default: UTC) TIME_ZONE = 'UTC' diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index cc0a3c016..3a494093b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -137,6 +137,7 @@ STORAGE_BACKEND = getattr(configuration, 'STORAGE_BACKEND', None) STORAGE_CONFIG = getattr(configuration, 'STORAGE_CONFIG', {}) TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a') TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC') +ENABLE_LOCALIZATION = getattr(configuration, 'ENABLE_LOCALIZATION', False) # Check for hard-coded dynamic config parameters for param in PARAMS: @@ -356,6 +357,9 @@ MIDDLEWARE = [ 'django_prometheus.middleware.PrometheusAfterMiddleware', ] +if not ENABLE_LOCALIZATION: + MIDDLEWARE.remove("django.middleware.locale.LocaleMiddleware") + ROOT_URLCONF = 'netbox.urls' TEMPLATES_DIR = BASE_DIR + '/templates' @@ -651,6 +655,13 @@ RQ_QUEUES.update({ queue: RQ_PARAMS for queue in set(QUEUE_MAPPINGS.values()) if queue not in RQ_QUEUES }) +# +# Localization +# + +if not ENABLE_LOCALIZATION: + USE_I18N = False + USE_L10N = False # # Plugins From 5975dbcb07e4d40d8447ff105790d7e8679000fd Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Fri, 30 Dec 2022 19:01:51 +0100 Subject: [PATCH 16/21] Fix component traces all pointing to the interface trace URL --- netbox/templates/dcim/consoleport.html | 2 +- netbox/templates/dcim/consoleserverport.html | 2 +- netbox/templates/dcim/inc/connection_endpoints.html | 2 +- netbox/templates/dcim/interface.html | 2 +- netbox/templates/dcim/powerfeed.html | 2 +- netbox/templates/dcim/poweroutlet.html | 2 +- netbox/templates/dcim/powerport.html | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/netbox/templates/dcim/consoleport.html b/netbox/templates/dcim/consoleport.html index ad4f15c9d..dee57e28b 100644 --- a/netbox/templates/dcim/consoleport.html +++ b/netbox/templates/dcim/consoleport.html @@ -60,7 +60,7 @@ {% if object.mark_connected %} Marked as connected {% elif object.cable %} - {% include 'dcim/inc/connection_endpoints.html' %} + {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:consoleport_trace' %} {% else %}
Not Connected diff --git a/netbox/templates/dcim/consoleserverport.html b/netbox/templates/dcim/consoleserverport.html index a543cd5ff..cd447d11d 100644 --- a/netbox/templates/dcim/consoleserverport.html +++ b/netbox/templates/dcim/consoleserverport.html @@ -60,7 +60,7 @@ {% if object.mark_connected %} Marked as connected {% elif object.cable %} - {% include 'dcim/inc/connection_endpoints.html' %} + {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:consoleserverport_trace' %} {% else %}
Not Connected diff --git a/netbox/templates/dcim/inc/connection_endpoints.html b/netbox/templates/dcim/inc/connection_endpoints.html index fb994a492..fb395ca74 100644 --- a/netbox/templates/dcim/inc/connection_endpoints.html +++ b/netbox/templates/dcim/inc/connection_endpoints.html @@ -3,7 +3,7 @@ Cable {{ object.cable|linkify }} - + diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index b593b7c00..f10e60490 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -145,7 +145,7 @@ Marked as Connected
{% elif object.cable %} - {% include 'dcim/inc/connection_endpoints.html' %} + {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:interface_trace' %} {% elif object.wireless_link %} diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index 6387c111d..4b8a6dc34 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -112,7 +112,7 @@ Marked as connected {% elif object.cable %} - {% include 'dcim/inc/connection_endpoints.html' %} + {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:powerfeed_trace' %} {% else %}
Not connected diff --git a/netbox/templates/dcim/poweroutlet.html b/netbox/templates/dcim/poweroutlet.html index fb6de8ddb..b7ac8ba8e 100644 --- a/netbox/templates/dcim/poweroutlet.html +++ b/netbox/templates/dcim/poweroutlet.html @@ -66,7 +66,7 @@ Marked as Connected
{% elif object.cable %} - {% include 'dcim/inc/connection_endpoints.html' %} + {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:poweroutlet_trace' %} {% else %}
Not Connected diff --git a/netbox/templates/dcim/powerport.html b/netbox/templates/dcim/powerport.html index 476ee44d3..d0ee779cb 100644 --- a/netbox/templates/dcim/powerport.html +++ b/netbox/templates/dcim/powerport.html @@ -66,7 +66,7 @@ Marked as Connected
{% elif object.cable %} - {% include 'dcim/inc/connection_endpoints.html' %} + {% include 'dcim/inc/connection_endpoints.html' with trace_url='dcim:powerport_trace' %} {% else %}
Not Connected From e1169e7ea6e0ae77ca113bf3f673736b9f88417c Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Tue, 3 Jan 2023 15:14:25 +0100 Subject: [PATCH 17/21] Fixes #11345 - Fix module validation (#11346) * Make sure we bail out if field validation failed when importing modules * Tweak form validation logic Co-authored-by: jeremystretch --- netbox/dcim/forms/common.py | 9 +++++---- netbox/dcim/models/devices.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/forms/common.py b/netbox/dcim/forms/common.py index d479916d9..a2243ce2d 100644 --- a/netbox/dcim/forms/common.py +++ b/netbox/dcim/forms/common.py @@ -56,8 +56,8 @@ class ModuleCommonForm(forms.Form): def clean(self): super().clean() - replicate_components = self.cleaned_data.get("replicate_components") - adopt_components = self.cleaned_data.get("adopt_components") + replicate_components = self.cleaned_data.get('replicate_components') + adopt_components = self.cleaned_data.get('adopt_components') device = self.cleaned_data.get('device') module_type = self.cleaned_data.get('module_type') module_bay = self.cleaned_data.get('module_bay') @@ -65,8 +65,9 @@ class ModuleCommonForm(forms.Form): if adopt_components: self.instance._adopt_components = True - # Bail out if we are not installing a new module or if we are not replicating components - if self.instance.pk or not replicate_components: + # Bail out if we are not installing a new module or if we are not replicating components (or if + # validation has already failed) + if self.errors or self.instance.pk or not replicate_components: self.instance._disable_replication = True return diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 53c6d12a7..603129228 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -961,7 +961,7 @@ class Module(PrimaryModel, ConfigContextModel): def clean(self): super().clean() - if self.module_bay.device != self.device: + if hasattr(self, "module_bay") and (self.module_bay.device != self.device): raise ValidationError( f"Module must be installed within a module bay belonging to the assigned device ({self.device})." ) From 1c636ea1273b45930760fbfc1ea290a15433fb7a Mon Sep 17 00:00:00 2001 From: Christian Harendt <22472485+christianharendt@users.noreply.github.com> Date: Thu, 29 Dec 2022 16:31:06 +0100 Subject: [PATCH 18/21] add username for redis authentication --- docs/configuration/required-parameters.md | 3 +++ netbox/netbox/configuration_example.py | 2 ++ netbox/netbox/configuration_testing.py | 2 ++ netbox/netbox/settings.py | 6 +++++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/configuration/required-parameters.md b/docs/configuration/required-parameters.md index 15f743754..a71a1b410 100644 --- a/docs/configuration/required-parameters.md +++ b/docs/configuration/required-parameters.md @@ -63,6 +63,7 @@ Redis is configured using a configuration setting similar to `DATABASE` and thes * `HOST` - Name or IP address of the Redis server (use `localhost` if running locally) * `PORT` - TCP port of the Redis service; leave blank for default port (6379) +* `USERNAME` - Redis username (if set) * `PASSWORD` - Redis password (if set) * `DATABASE` - Numeric database ID * `SSL` - Use SSL connection to Redis @@ -75,6 +76,7 @@ REDIS = { 'tasks': { 'HOST': 'redis.example.com', 'PORT': 1234, + 'USERNAME': 'netbox' 'PASSWORD': 'foobar', 'DATABASE': 0, 'SSL': False, @@ -82,6 +84,7 @@ REDIS = { 'caching': { 'HOST': 'localhost', 'PORT': 6379, + 'USERNAME': '' 'PASSWORD': '', 'DATABASE': 1, 'SSL': False, diff --git a/netbox/netbox/configuration_example.py b/netbox/netbox/configuration_example.py index 9d9651462..262b52d4f 100644 --- a/netbox/netbox/configuration_example.py +++ b/netbox/netbox/configuration_example.py @@ -31,6 +31,7 @@ REDIS = { # Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel # 'SENTINELS': [('mysentinel.redis.example.com', 6379)], # 'SENTINEL_SERVICE': 'netbox', + 'USERNAME': '', 'PASSWORD': '', 'DATABASE': 0, 'SSL': False, @@ -44,6 +45,7 @@ REDIS = { # Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel # 'SENTINELS': [('mysentinel.redis.example.com', 6379)], # 'SENTINEL_SERVICE': 'netbox', + 'USERNAME': '', 'PASSWORD': '', 'DATABASE': 1, 'SSL': False, diff --git a/netbox/netbox/configuration_testing.py b/netbox/netbox/configuration_testing.py index 621671f04..26d768004 100644 --- a/netbox/netbox/configuration_testing.py +++ b/netbox/netbox/configuration_testing.py @@ -22,6 +22,7 @@ REDIS = { 'tasks': { 'HOST': 'localhost', 'PORT': 6379, + 'USERNAME': '', 'PASSWORD': '', 'DATABASE': 0, 'SSL': False, @@ -29,6 +30,7 @@ REDIS = { 'caching': { 'HOST': 'localhost', 'PORT': 6379, + 'USERNAME': '', 'PASSWORD': '', 'DATABASE': 1, 'SSL': False, diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 3a494093b..b4f4ac15d 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -230,6 +230,7 @@ TASKS_REDIS_USING_SENTINEL = all([ ]) TASKS_REDIS_SENTINEL_SERVICE = TASKS_REDIS.get('SENTINEL_SERVICE', 'default') TASKS_REDIS_SENTINEL_TIMEOUT = TASKS_REDIS.get('SENTINEL_TIMEOUT', 10) +TASKS_REDIS_USERNAME = TASKS_REDIS.get('USERNAME', '') TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '') TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0) TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False) @@ -243,6 +244,8 @@ if 'caching' not in REDIS: CACHING_REDIS_HOST = REDIS['caching'].get('HOST', 'localhost') CACHING_REDIS_PORT = REDIS['caching'].get('PORT', 6379) CACHING_REDIS_DATABASE = REDIS['caching'].get('DATABASE', 0) +CACHING_REDIS_USERNAME = REDIS['caching'].get('USERNAME', '') +CACHING_REDIS_USERNAME_HOST = '@'.join(filter(None, [CACHING_REDIS_USERNAME, CACHING_REDIS_HOST])) CACHING_REDIS_PASSWORD = REDIS['caching'].get('PASSWORD', '') CACHING_REDIS_SENTINELS = REDIS['caching'].get('SENTINELS', []) CACHING_REDIS_SENTINEL_SERVICE = REDIS['caching'].get('SENTINEL_SERVICE', 'default') @@ -252,7 +255,7 @@ CACHING_REDIS_SKIP_TLS_VERIFY = REDIS['caching'].get('INSECURE_SKIP_TLS_VERIFY', CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}', + 'LOCATION': f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_USERNAME_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'PASSWORD': CACHING_REDIS_PASSWORD, @@ -640,6 +643,7 @@ else: } RQ_PARAMS.update({ 'DB': TASKS_REDIS_DATABASE, + 'USERNAME': TASKS_REDIS_USERNAME, 'PASSWORD': TASKS_REDIS_PASSWORD, 'DEFAULT_TIMEOUT': RQ_DEFAULT_TIMEOUT, }) From b9f8370097cc1d858271c9f4a2db59bcc6fd9f4e Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:13:34 +0100 Subject: [PATCH 19/21] Fixes #11156 - Allow InventoryItem component reassignment (#11256) * Allow re-assigning InventoryItem components * Refactor logic for finding initial component assignment on InventoryItems * PEP8 fix * Fix wrong HTML causing tab list to extend past the end of the parent row * Tweak form field labels Co-authored-by: jeremystretch --- netbox/dcim/forms/model_forms.py | 111 ++++++++++++++++-- netbox/dcim/models/device_components.py | 5 + netbox/dcim/views.py | 13 +- netbox/templates/dcim/inventoryitem_edit.html | 106 +++++++++++++++++ 4 files changed, 212 insertions(+), 23 deletions(-) create mode 100644 netbox/templates/dcim/inventoryitem_edit.html diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 1614f4bae..91e0266f0 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1549,15 +1549,63 @@ class InventoryItemForm(DeviceComponentForm): queryset=Manufacturer.objects.all(), required=False ) - component_type = ContentTypeChoiceField( - queryset=ContentType.objects.all(), - limit_choices_to=MODULAR_COMPONENT_MODELS, + + # Assigned component selectors + consoleport = DynamicModelChoiceField( + queryset=ConsolePort.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_id': '$device' + }, + label=_('Console port') ) - component_id = forms.IntegerField( + consoleserverport = DynamicModelChoiceField( + queryset=ConsoleServerPort.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_id': '$device' + }, + label=_('Console server port') + ) + frontport = DynamicModelChoiceField( + queryset=FrontPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Front port') + ) + interface = DynamicModelChoiceField( + queryset=Interface.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Interface') + ) + poweroutlet = DynamicModelChoiceField( + queryset=PowerOutlet.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Power outlet') + ) + powerport = DynamicModelChoiceField( + queryset=PowerPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Power port') + ) + rearport = DynamicModelChoiceField( + queryset=RearPort.objects.all(), + required=False, + query_params={ + 'device_id': '$device' + }, + label=_('Rear port') ) fieldsets = ( @@ -1565,22 +1613,61 @@ class InventoryItemForm(DeviceComponentForm): ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')), ) + class Meta: + model = InventoryItem + fields = [ + 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', + 'description', 'tags', + ] + def __init__(self, *args, **kwargs): + instance = kwargs.get('instance') + initial = kwargs.get('initial', {}).copy() + component_type = initial.get('component_type') + component_id = initial.get('component_id') + + # Used for picking the default active tab for component selection + self.no_component = True + + if instance: + # When editing set the initial value for component selectin + for component_model in ContentType.objects.filter(MODULAR_COMPONENT_MODELS): + if type(instance.component) is component_model.model_class(): + initial[component_model.model] = instance.component + self.no_component = False + break + elif component_type and component_id: + # When adding the InventoryItem from a component page + if content_type := ContentType.objects.filter(MODULAR_COMPONENT_MODELS).filter(pk=component_type).first(): + if component := content_type.model_class().objects.filter(pk=component_id).first(): + initial[content_type.model] = component + self.no_component = False + + kwargs['initial'] = initial + super().__init__(*args, **kwargs) # Specifically allow editing the device of IntentoryItems if self.instance.pk: self.fields['device'].disabled = False - class Meta: - model = InventoryItem - fields = [ - 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', - 'description', 'component_type', 'component_id', 'tags', + def clean(self): + super().clean() + + # Handle object assignment + selected_objects = [ + field for field in ( + 'consoleport', 'consoleserverport', 'frontport', 'interface', 'poweroutlet', 'powerport', 'rearport' + ) if self.cleaned_data[field] ] + if len(selected_objects) > 1: + raise forms.ValidationError("An InventoryItem can only be assigned to a single component.") + elif selected_objects: + self.instance.component = self.cleaned_data[selected_objects[0]] + else: + self.instance.component = None -# # Device component roles # diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 658423e52..26a6ade98 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1146,3 +1146,8 @@ class InventoryItem(MPTTModel, ComponentModel): # When moving an InventoryItem to another device, remove any associated component if self.component and self.component.device != self.device: self.component = None + else: + if self.component and self.component.device != self.device: + raise ValidationError({ + "device": "Cannot assign inventory item to component on another device" + }) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 870cbcc18..115c16112 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2914,23 +2914,14 @@ class InventoryItemView(generic.ObjectView): class InventoryItemEditView(generic.ObjectEditView): queryset = InventoryItem.objects.all() form = forms.InventoryItemForm + template_name = 'dcim/inventoryitem_edit.html' class InventoryItemCreateView(generic.ComponentCreateView): queryset = InventoryItem.objects.all() form = forms.InventoryItemCreateForm model_form = forms.InventoryItemForm - - def alter_object(self, instance, request): - # Set component (if any) - component_type = request.GET.get('component_type') - component_id = request.GET.get('component_id') - - if component_type and component_id: - content_type = get_object_or_404(ContentType, pk=component_type) - instance.component = get_object_or_404(content_type.model_class(), pk=component_id) - - return instance + template_name = 'dcim/inventoryitem_edit.html' @register_model_view(InventoryItem, 'delete') diff --git a/netbox/templates/dcim/inventoryitem_edit.html b/netbox/templates/dcim/inventoryitem_edit.html new file mode 100644 index 000000000..006b6f008 --- /dev/null +++ b/netbox/templates/dcim/inventoryitem_edit.html @@ -0,0 +1,106 @@ +{% extends 'generic/object_edit.html' %} +{% load static %} +{% load form_helpers %} +{% load helpers %} + +{% block form %} +
+
+
InventoryItem
+
+ {% render_field form.device %} + {% render_field form.parent %} + {% render_field form.name %} + {% render_field form.label %} + {% render_field form.role %} + {% render_field form.description %} + {% render_field form.tags %} +
+ +
+
+
Hardware
+
+ {% render_field form.manufacturer %} + {% render_field form.part_id %} + {% render_field form.serial %} + {% render_field form.asset_tag %} +
+ +
+
+
Component Assignment
+
+
+ +
+
+
+ {% render_field form.consoleport %} +
+
+ {% render_field form.consoleserverport %} +
+
+ {% render_field form.frontport %} +
+
+ {% render_field form.interface %} +
+
+ {% render_field form.poweroutlet %} +
+
+ {% render_field form.powerport %} +
+
+ {% render_field form.rearport %} +
+
+
+ + {% if form.custom_fields %} +
+
+
Custom Fields
+
+ {% render_custom_fields form %} +
+ {% endif %} +{% endblock %} From 1c72a80d9af29411b7ec89d3c1bb9909b41d1c72 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 3 Jan 2023 10:21:19 -0500 Subject: [PATCH 20/21] Changelog for #11156, #11259, #11342, #11345 --- docs/release-notes/version-3.4.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index fb2eec5dd..94d67a36f 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -7,15 +7,19 @@ * [#9285](https://github.com/netbox-community/netbox/issues/9285) - Enable specifying assigned component during bulk import of inventory items * [#10700](https://github.com/netbox-community/netbox/issues/10700) - Match device name when using modules quick search * [#11121](https://github.com/netbox-community/netbox/issues/11121) - Add VM resource totals to cluster view +* [#11156](https://github.com/netbox-community/netbox/issues/11156) - Enable selecting assigned component when editing inventory item in UI * [#11223](https://github.com/netbox-community/netbox/issues/11223) - `reindex` management command should accept app label without model name * [#11244](https://github.com/netbox-community/netbox/issues/11244) - Add controls for saved filters to rack elevations list * [#11248](https://github.com/netbox-community/netbox/issues/11248) - Fix database migration when plugin with search indexer is enabled +* [#11259](https://github.com/netbox-community/netbox/issues/11259) - Add support for Redis username configuration ### Bug Fixes * [#11280](https://github.com/netbox-community/netbox/issues/11280) - Fix errant newlines when exporting interfaces with multiple IP addresses assigned * [#11290](https://github.com/netbox-community/netbox/issues/11290) - Correct reporting of scheduled job duration * [#11232](https://github.com/netbox-community/netbox/issues/11232) - Enable partial & regular expression matching for non-string types in global search +* [#11342](https://github.com/netbox-community/netbox/issues/11342) - Correct cable trace URL under "connection" tab for device components +* [#11345](https://github.com/netbox-community/netbox/issues/11345) - Fix form validation for bulk import of modules --- From e940f00c014c7e72b2425307d818600712828023 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 3 Jan 2023 16:13:11 -0500 Subject: [PATCH 21/21] Release v3.4.2 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.4.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 89cb0b88e..c5b45c31c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.1 + placeholder: v3.4.2 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index ed84ff821..54a7735c5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.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: v3.4.1 + placeholder: v3.4.2 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 94d67a36f..e8f3e2bba 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -1,6 +1,6 @@ # NetBox v3.4 -## v3.4.2 (FUTURE) +## v3.4.2 (2023-01-03) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index b4f4ac15d..0ad211416 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -24,7 +24,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.4.2-dev' +VERSION = '3.4.2' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index 5a0e74669..7c2c129d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ bleach==5.0.1 -Django==4.1.4 +Django==4.1.5 django-cors-headers==3.13.0 django-debug-toolbar==3.8.1 django-filter==22.1 @@ -10,7 +10,7 @@ django-prometheus==2.2.0 django-redis==5.2.0 django-rich==1.4.0 django-rq==2.6.0 -django-tables2==2.4.1 +django-tables2==2.5.0 django-taggit==3.1.0 django-timezone-field==5.0 djangorestframework==3.14.0 @@ -22,10 +22,10 @@ Markdown==3.3.7 mkdocs-material==8.5.11 mkdocstrings[python-legacy]==0.19.1 netaddr==0.8.0 -Pillow==9.3.0 +Pillow==9.4.0 psycopg2-binary==2.9.5 PyYAML==6.0 -sentry-sdk==1.11.1 +sentry-sdk==1.12.1 social-auth-app-django==5.0.0 social-auth-core[openidconnect]==4.3.0 svgwrite==1.4.3