From fa014fcbf0aa03d7cdd822ff3aa38e2824773d48 Mon Sep 17 00:00:00 2001 From: Craig Pund Date: Thu, 30 Jun 2022 01:38:38 -0400 Subject: [PATCH 01/34] add device bulk rename view and url --- netbox/dcim/urls.py | 1 + netbox/dcim/views.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c5cd0fa65..f00bd73e1 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -248,6 +248,7 @@ urlpatterns = [ path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'), path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'), path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'), + path('devices/rename/', views.DeviceBulkRenameView.as_view(), name='device_bulk_rename'), path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'), path('devices//', views.DeviceView.as_view(), name='device'), path('devices//edit/', views.DeviceEditView.as_view(), name='device_edit'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 35a1056b2..28325bcfc 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1786,6 +1786,12 @@ class DeviceBulkDeleteView(generic.BulkDeleteView): table = tables.DeviceTable +class DeviceBulkRenameView(generic.BulkRenameView): + queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') + filterset = filtersets.DeviceFilterSet + table = tables.DeviceTable + + # # Devices # From 5dff7433e854a09cb876602ce8220f8f5479a80a Mon Sep 17 00:00:00 2001 From: Craig Pund Date: Thu, 30 Jun 2022 01:38:53 -0400 Subject: [PATCH 02/34] add bulk device rename button to device_list --- netbox/templates/dcim/device_list.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html index 60efc842e..e5dbe0d9c 100644 --- a/netbox/templates/dcim/device_list.html +++ b/netbox/templates/dcim/device_list.html @@ -2,6 +2,10 @@ {% block bulk_buttons %} {% if perms.dcim.change_device %} + + From 7d6882bec29f2ffe70a278cee99f0abc9216d498 Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Date: Sat, 23 Jul 2022 20:24:33 +0000 Subject: [PATCH 06/34] Change display to Modal --- netbox/templates/ipam/prefix.html | 84 +++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index caad547c4..ed2c3339b 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -143,34 +143,16 @@ {% endwith %} - {% if object.prefix.version == 4 %} - - - Network Mask - - - {{ object.prefix.netmask }} - - - - - Broadcast Address - - - {{ object.prefix.broadcast }} - - - - - Wildcard Mask - - - {{ object.prefix.hostmask }} - - - {% endif %} + {% if object.prefix.version == 4 %} + + {% endif %} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} @@ -186,4 +168,54 @@ {% plugin_full_width_page object %} +{% if object.prefix.version == 4 %} + +{% endif %} {% endblock %} From 064d7f3bd0a803ed55198455e54a29c8f7c0adc2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 8 Aug 2022 15:34:13 -0400 Subject: [PATCH 07/34] PRVB --- docs/release-notes/version-3.2.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index bf6f2f848..56a35cc02 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -1,5 +1,9 @@ # NetBox v3.2 +## v3.2.9 (FUTURE) + +--- + ## v3.2.8 (2022-08-08) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 12ab44399..d38289d43 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str # Environment setup # -VERSION = '3.2.8' +VERSION = '3.2.9-dev' # Hostname HOSTNAME = platform.node() From c7faca948016caa8ce30b3571c8a4e71dc33f70e Mon Sep 17 00:00:00 2001 From: gildarov Date: Tue, 9 Aug 2022 11:56:19 +0300 Subject: [PATCH 08/34] fix typo in virtualization/forms/filtersets.py --- netbox/virtualization/forms/filtersets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/virtualization/forms/filtersets.py b/netbox/virtualization/forms/filtersets.py index 88aa1a6c2..2f52850bd 100644 --- a/netbox/virtualization/forms/filtersets.py +++ b/netbox/virtualization/forms/filtersets.py @@ -89,7 +89,7 @@ class VirtualMachineFilterForm( (None, ('q', 'tag')), ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id')), ('Location', ('region_id', 'site_group_id', 'site_id')), - ('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), + ('Attributes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), ) From c24f1f14eccee2eecc67fc8d4df1db6d3e20b679 Mon Sep 17 00:00:00 2001 From: Barnabas Lovas Date: Wed, 10 Aug 2022 13:22:58 +0200 Subject: [PATCH 09/34] Closes #9625: Add Contact Phone/Email to quick view panes to save time --- netbox/templates/inc/panels/contacts.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/netbox/templates/inc/panels/contacts.html b/netbox/templates/inc/panels/contacts.html index 26961f04a..359ad8d7e 100644 --- a/netbox/templates/inc/panels/contacts.html +++ b/netbox/templates/inc/panels/contacts.html @@ -10,6 +10,8 @@ Name Role Priority + Phone + Email {% for contact in contacts %} @@ -17,6 +19,20 @@ {{ contact.contact|linkify }} {{ contact.role|placeholder }} {{ contact.get_priority_display|placeholder }} + + {% if contact.contact.phone %} + {{ contact.contact.phone }} + {% else %} + {{ ''|placeholder }} + {% endif %} + + + {% if contact.contact.email %} + {{ contact.contact.email }} + {% else %} + {{ ''|placeholder }} + {% endif %} + {% if perms.tenancy.change_contactassignment %} From 1c7ef73d1fcff334ff34dba2fff073c934a06c99 Mon Sep 17 00:00:00 2001 From: Dorian Bourgeoisat Date: Tue, 9 Aug 2022 14:20:42 +0200 Subject: [PATCH 10/34] Closes #8595: Added new PON interface types --- netbox/dcim/choices.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 2e96f9c67..c91faf3da 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -814,6 +814,14 @@ class InterfaceTypeChoices(ChoiceSet): # ATM/DSL TYPE_XDSL = 'xdsl' + # PON + TYPE_GPON = 'gpon' + TYPE_XG_PON = 'xg-pon' + TYPE_XGS_PON = 'xgs-pon' + TYPE_TWDM_PON = 'twdm-pon' + TYPE_EPON = 'epon' + TYPE_10G_EPON = '10g-epon' + # Stacking TYPE_STACKWISE = 'cisco-stackwise' TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus' @@ -950,6 +958,17 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_XDSL, 'xDSL'), ) ), + ( + 'PON', + ( + (TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gps)'), + (TYPE_XG_PON, 'XG-PON (10 Gbps / 2.5 Gbps)'), + (TYPE_XGS_PON, 'XGS-PON (10 Gbps)'), + (TYPE_TWDM_PON, 'TWDM-PON (NG-PON2) (4x10 Gbps)'), + (TYPE_EPON, 'EPON (1 Gbps)'), + (TYPE_10G_EPON, '10G-EPON (10 Gbps)'), + ) + ), ( 'Stacking', ( From 8f1e70f01d761f65382bb01d568e5f60ae47aa68 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 10 Aug 2022 15:24:45 -0400 Subject: [PATCH 11/34] Fixes #9961: Correct typo --- docs/models/dcim/frontport.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/models/dcim/frontport.md b/docs/models/dcim/frontport.md index 0b753c012..6f12e8cbf 100644 --- a/docs/models/dcim/frontport.md +++ b/docs/models/dcim/frontport.md @@ -1,3 +1,3 @@ ## Front Ports -Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple rear ports, using numeric positions to annotate the specific alignment of each. +Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple front ports, using numeric positions to annotate the specific alignment of each. From a9aaa8939c2bc15ff59f82d547ae4daa45234561 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 10 Aug 2022 16:08:52 -0400 Subject: [PATCH 12/34] Closes #9161: Pretty print JSON custom field data when editing --- docs/release-notes/version-3.2.md | 2 ++ netbox/extras/models/customfields.py | 4 ++-- netbox/project-static/dist/netbox-dark.css | Bin 374410 -> 374221 bytes netbox/project-static/dist/netbox-light.css | Bin 232175 -> 232088 bytes netbox/project-static/dist/netbox-print.css | Bin 727867 -> 727384 bytes netbox/project-static/styles/netbox.scss | 5 +---- netbox/utilities/forms/fields/fields.py | 1 + netbox/utilities/forms/forms.py | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 56a35cc02..a5bd05cde 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -2,6 +2,8 @@ ## v3.2.9 (FUTURE) +* [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing + --- ## v3.2.8 (2022-08-08) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index b7d77e550..55b7a9f03 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -18,7 +18,7 @@ from netbox.models.features import ExportTemplatesMixin, WebhooksMixin from utilities import filters from utilities.forms import ( CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, - LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice, + JSONField, LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice, ) from utilities.querysets import RestrictedQuerySet from utilities.validators import validate_regex @@ -343,7 +343,7 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): # JSON elif self.type == CustomFieldTypeChoices.TYPE_JSON: - field = forms.JSONField(required=required, initial=initial) + field = JSONField(required=required, initial=initial) # Object elif self.type == CustomFieldTypeChoices.TYPE_OBJECT: diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index b929f176a6c67c2067c4f29044f724d50ccbd60b..c6e69db89fe9d6f02b4d9600fc1d70b2ca3c7bab 100644 GIT binary patch delta 44 zcmeBrD|Ys^SVIeA3sVbo3(FQ(>*>>Lrn8z%zQ2i=GdeFZH`O+|xNN)A3|0?00G8Pi A9{>OV delta 108 zcmX^6TCD4>SVIeA3sVbo3(FQ(>*-?3nJMu(`N@en@yYplC8-r9@hOQViIWdXs!Yy* z#xdPyI;$}+NKHX$Qch-ae0J*e##Ba?$$8I2r`Jqp<$@{{;fl^n%uTfg>D}Hsoz+7Q E0PR&NdH?_b diff --git a/netbox/project-static/dist/netbox-light.css b/netbox/project-static/dist/netbox-light.css index 341369adff9033ea697bf89ab50ae0c592c00e73..c3f8b80a69ea3e2cd756bc65c9a32709a7af0d4f 100644 GIT binary patch delta 40 wcmaDqm2bvWzJ?aY7N#xCyGuFsaubWPQ}WC6CKq;!O&2I*=Gtyu#=J!Y07M%O+5i9m delta 109 zcmbO+mGAvjzJ?aY7N#xCyG!krGgIPo@{<#D;*<09N>VFI;!_e!5_LeV#G=$hz1+m2 y?3DcSJh%u*LqTa$PG)j^c53D1!cMX2w@R70Kn$+vyu{p8TaW?UAD1$36#)QFI4eQ` diff --git a/netbox/project-static/dist/netbox-print.css b/netbox/project-static/dist/netbox-print.css index 61bcedf9c0ec303c353a88390cdb00e18b3f5aaf..3b10f5c27c1c4d3fea800feadc6b56b2fb229115 100644 GIT binary patch delta 101 zcmV-r0Gj{1wk+7PEP#XogaU*Egam{Iga(8Mgb0KQvuC6iz(2b1tR2bb{M3N(|@EeMw&-3kbox!ejgm#F7M2#)7Pc1l7LFFqEnLmF#FaBs;&bwo6LaE|^YcnlD@x*1 z5=#%n@fJW`fV=V=^FwWdAOqCj@~}+HrFm&09QI|KL7v# diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index a54b6c324..8ef280397 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -714,11 +714,8 @@ textarea.form-control[rows='10'] { height: 18rem; } -textarea#id_local_context_data, textarea.markdown, -textarea#id_public_key, -textarea.form-control[name='csv'], -textarea.form-control[name='data'] { +textarea.form-control[name='csv'] { font-family: $font-family-monospace; } diff --git a/netbox/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py index 9168189a1..df69339e5 100644 --- a/netbox/utilities/forms/fields/fields.py +++ b/netbox/utilities/forms/fields/fields.py @@ -99,6 +99,7 @@ class JSONField(_JSONField): if not self.help_text: self.help_text = 'Enter context data in JSON format.' self.widget.attrs['placeholder'] = '' + self.widget.attrs['class'] = 'font-monospace' def prepare_value(self, value): if isinstance(value, InvalidJSONInput): diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 3b5cd8308..8ad6f103b 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -136,7 +136,7 @@ class ImportForm(BootstrapMixin, forms.Form): Generic form for creating an object from JSON/YAML data """ data = forms.CharField( - widget=forms.Textarea, + widget=forms.Textarea(attrs={'class': 'font-monospace'}), help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported." ) format = forms.ChoiceField( From aabe8f7c5b4b61ca87623b496d127266aa443a52 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 10 Aug 2022 16:18:30 -0400 Subject: [PATCH 13/34] Changelog for #9625 --- docs/release-notes/version-3.2.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index a5bd05cde..598ee7874 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -2,7 +2,10 @@ ## v3.2.9 (FUTURE) +### Enhancements + * [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing +* [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel --- From 9a80a491c90bcdc3341a7c75f2f8bae93e32fba9 Mon Sep 17 00:00:00 2001 From: Christoph Schneider Date: Thu, 11 Aug 2022 14:11:41 +0200 Subject: [PATCH 14/34] re-enable markdown in custom columns --- netbox/netbox/tables/columns.py | 25 +++++++++++++++++++++++++ netbox/netbox/tables/tables.py | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index f78b9f37c..cc20bdd0c 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -550,3 +550,28 @@ class MarkdownColumn(tables.TemplateColumn): def value(self, value): return value + + +class CustomFieldMarkdownColumn(tables.TemplateColumn): + """ + Render a Markdown string in a longtext custom column. + """ + template_code = """ + {% if value %} + {{ value|markdown }} + {% else %} + — + {% endif %} + """ + + def __init__(self, customfield, *args, **kwargs): + self.customfield = customfield + kwargs['accessor'] = Accessor(f'custom_field_data__{customfield.name}') + kwargs['template_code'] = self.template_code + if 'verbose_name' not in kwargs: + kwargs['verbose_name'] = customfield.label or customfield.name + + super().__init__(*args, **kwargs) + + def value(self, value): + return value diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 8c5fb039c..d55038c4d 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -7,6 +7,7 @@ from django.db.models.fields.related import RelatedField from django_tables2.data import TableQuerysetData from extras.models import CustomField, CustomLink +from extras.choices import CustomFieldTypeChoices from netbox.tables import columns from utilities.paginator import EnhancedPaginator, get_paginate_count @@ -180,7 +181,7 @@ class NetBoxTable(BaseTable): content_type = ContentType.objects.get_for_model(self._meta.model) custom_fields = CustomField.objects.filter(content_types=content_type) extra_columns.extend([ - (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields + (f'cf_{cf.name}', columns.CustomFieldMarkdownColumn(cf) if cf.type == CustomFieldTypeChoices.TYPE_LONGTEXT else columns.CustomFieldColumn(cf)) for cf in custom_fields ]) custom_links = CustomLink.objects.filter(content_type=content_type, enabled=True) extra_columns.extend([ From f74b7aa7acb6ee9f84e9d4ff237c33bac92cc0d8 Mon Sep 17 00:00:00 2001 From: Jonathan Senecal Date: Tue, 9 Aug 2022 17:32:20 -0400 Subject: [PATCH 15/34] Add a "clear" button for quick search Fixes #9857 --- netbox/project-static/dist/netbox-dark.css | Bin 374221 -> 375157 bytes netbox/project-static/dist/netbox-light.css | Bin 232088 -> 232694 bytes netbox/project-static/dist/netbox-print.css | Bin 727384 -> 729266 bytes netbox/project-static/dist/netbox.js | Bin 375907 -> 376463 bytes netbox/project-static/dist/netbox.js.map | Bin 345348 -> 345789 bytes netbox/project-static/src/search.ts | 44 ++++++ netbox/project-static/styles/netbox.scss | 21 +++ netbox/project-static/styles/overrides.scss | 8 + netbox/project-static/styles/theme-dark.scss | 4 + netbox/project-static/styles/theme-light.scss | 3 +- netbox/project-static/styles/utilities.scss | 6 + netbox/templates/dcim/device/interfaces.html | 145 +++++++++--------- netbox/templates/inc/table_controls_htmx.html | 34 ++-- 13 files changed, 169 insertions(+), 96 deletions(-) diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index c6e69db89fe9d6f02b4d9600fc1d70b2ca3c7bab..94718ac407f4e5b8c9acba2d7c97c9936fc79765 100644 GIT binary patch delta 597 zcmb7=y-EW?0E9{IqJn~j_=8%U8Vk?tkwmoEpwFPCh~Q>#^_E~Y+5P~z3FHi+O4B!&X1yYpj>QO9d z|Dk~q!^8*8-G#OFKg0!*p|4~=zx9v&HyDfb&6PQ&#e$jDZDu4|&mFTJcN~wp1o%iQ zfIaHF798lPL?z8rpQ;M%#azPl4oZMl@1=Tu#5oNqE@W5s4GZfKQGU1RL3)yhMuu7YZ?zI|r1xqzp6Z^pML#|?q zxUB?YlJ?GKZ~$nfWr_y~_gF0_E3Nkdr1wUR)!m;=7W8*t%0)<@zwOs_p=$Ft(1P8a9>IA<6lW+wFx4Z2G Vh>n+>egzDdLUsZYw*!6!ayxcD8N>hp diff --git a/netbox/project-static/dist/netbox-light.css b/netbox/project-static/dist/netbox-light.css index c3f8b80a69ea3e2cd756bc65c9a32709a7af0d4f..bf0ba2f62028e5344fcee1c4c872304e047234d6 100644 GIT binary patch delta 560 zcma)2y-EW?7$kS3iJHP65UUjto9uZP5oAv-ZEXdy*xOsUk6d;yw|mhTBntWjBCEZR zpj_z-*yIuP39M|RG3qH6ru)8OW|*1ltLp1b_4K@SXZeF3h$Pedx|8rN#c9f-148g2 z4;rBH2*4p{GNl@%sExs9R4^XnAk%sb!6u7v%2gI?HLj#kT+UA~RTiO{$W&9Ib26~< z(uNa3$Z<@S#w>z3M5MU8ZBP(E47x+!X(1QBOeh<^?){5>OOeycfHg+V(cijVbJj1- znFr6BEPpV~_OrE6$7}WEs7T@;V-O)fBA)C1KxL+55S$1pxM}U$Gv;xpG-XyjYoe!9 zDijPyCPhZ%s@i8hF7M2#)7Pc1l7LF~PB~8;6^*DtmE4j!|SJ2}WQL@r4Pfg0s zEYU4aO)N^z&`nOvOHR$vO)4!Z$@np0AYp->!$!pVj%dN8YP z!3;5Ul@P0^H~KMZffe8~im(cxWg3$eSlM9~n!`D`EEQbMqK)5Df+{Ajb5Wi?t$|B^ zdciw3*6meIoI0$6dKsB1sk%9d#U;AQ8JRgL3ey|)Sv4kq;N;-PB4MD5uG|WtwYn^` zI5R0TC$pr|3aBh4HE()j8mCPiG%9ov7 zkidZulbSx!i$!$$0bX{l>GQkTM4>W;+MI?6rWQy~B-k8C0#SgZjOyIPqV&u>-JI04 z5-VL}qk;;>%-n+fqLReC66-46%)FG;3M)ec10>P>(vqCayi}{a{Jd0{@brXhED|6m zaBGzmCFT`pmSpDVSs^^CU{I`+Uyzs#^s|DVQLzHZfW+yB(u`ulm~K*-Zupl;3#)|S mVJ7VWEIO<*^2<_-s-ad`nHs2C6LyCok~>7Vzh2ChTM7Wc!7`fw delta 68 zcmV-K0K5OP!7SLaEP#XogaU*Egam{Iga(8Mgb1_=bb^-<;06Y_Cx!_u1((o|2?djh a1yYA~j0v}Oj0)}>mv62L2DeMA3TJgmrREbq?W&cZpT zAU^T#lKoO8GVOo89y!Hg^d;kTiZ7sYv6?BJKIl}ZBD`BPGD&0?A7)xnM#R{La-tn~ zpb_y1u3kQz5K<9gTLzkX)t)50P$@*xp-@BEsWyq9;73sK*sUzN9JL>--(QYUmpE{h zT{S$+w+v0tYIg{Rgj!SIGF0o!#-fQ-QmC|x=)gkp@%M^E;nun0*H>Lqo^c3GL)F!l zjYG8_S16<&)77op#p^WnRYSFYAtT-*T2YJmr8qXHahUHdbeo1jUcG(sYD<$sA@2Xt^>8G zO1$zwtbBBs?=N(#cL>2GbZ2o0Iq0^&e@N6FUe4+rYKyr2VDZe}VZKdn+$KK|cO7g* zJ>nAw7coW$dGaexz!l?aDB07Gg@Srizg?)T`Qs{Q5RheK?rQy4hLXV@?o(sF9A->rmw23EfT*~%4 z>g|OI&AzD%HH+uoRIN+uct>H_Oibvd!(?MZ3mvA3Bn-&m9iIM*HyDSG7;B5at5sDid#w;pG`dLmdcut zj<*!vN!gTT)#vGH^av?itvBjPhkcP09}$0i%Qh4dpSnf8*`wo)h4&IRp(+4l1#GhJ z^HH17M5W6(DP5Hb8>oB0CZ4!TgQBAKR#Sa4|CY-p1few_vI$*u_8yziLzgoRl+${@ zO<4plw5MfjEDwJh>di4gi=?f=8msfCBc4(9KY@6^+P&-xG>I0 zzDu`F7^RHNDXgd;o@C&Nt%2wkvt=a}Zdk+DCITl~I-HA-%nmf*_4D0yDLIY=h#qGdz%G;Kh;1n;oqv0&OP2CdHX!2Uuqv-LV zj;|`bngLpA-&vcY8%;65?XT|G0`RQ6vlt9Y^PMZJ-8#O%@P@-Cbkjj>`9TfvCS{yn zyzvrXK8b+O3ZRmKEurb;SHnl35a;1Erm`3rLyStHITz=2>yUjL!(3eX2 z5>3%WUC=fGE19UQK{*}YS0J||Pi9+8(9=a}&&y#KNH-;PIM1`@m|&;!8n+3}v0CVz zZ;|hO5q~t{@$ro|K`}IHdrI8&(}z*J_~lPkD_Rtx@I-2p(KNIhA6FOdg+@_(uVz7q zO*7_-G^8Q}zQ|4?5~=GFgZJLFAP(|46i8|cFAj^#?<-wu(($bY;+t%O)s+J4>I;Mb z7;X18uC21wHQ6*&lqY7LRc7tf@tuViM?s3I$?FxLxNj}nYg3Pkf4fhuvg`OAg*H9; zHr@F_!mcdeFADe9boa=rQlHnVNMx(Fp_|Tg4XB$|Q`b-Lry^G(Eb~1BQXHNzb!~b5 zZ?*|p%GW;e@%uL~Yg07Y6%m?k>S&}kbl5w9<6^Yc4w!Z5YHfiM{KMdG}l%|fkW!_hMV9DPTd0cY<$x)$W=?V|@5 z`LseSmD-e6Xrs#>(F!T?;s+hmeOehr#P=UGZqe)bP+{Kr1xUH=iD6;@V>NSZ$^~K0I_bZqH8&=kfCdD(ki;< z)Czt&ZM#<30n@G@npn3TfNH^?IRI{+_}MD(Yd`&2|4ye??T_$YkaL(!ZF0yJ9v}Bc zs;j+9K~r7rmRqRkBNbn%1;Ev+LlNE+;o(JDLGr+tO3U(T2IohnI!4DW5g$0fiVsWY zlm>NtxGQj~&@w9?{P{+}$rpdVT+EAcxRytz4-56)BxD6 zc|=t^tmCtV2No^3^3bPItN655t>QJAwThYr#j!{BS6Q`cU>qNd2u7`X2<}EAf<@+5 zI>N69Mnwd(sC~2y0)f^?A3$6j{FpG=zXBD{=um{I-Z8N@v&3zWEd-CU>oF78qT_8! zyJhVl%AgjtfQ}}YXNd383JFTsuvX|2UwUjY8Wul(tdeQhlA|ST8F{mWEiZ~H>(F$} z{CEj7XB3=|!n~@)@RMh^RB7`o7E}?sxFGl=&;wXU)a{D{IB34;<+3{Uoz5SgcIwl(Yco}8c zX^=sYcNv5@tq&PwH;(s+;iuOlk9hObKP^ccgo3A~k%LnR7(=4x*A0L#xBmKTlok*F z#*C8Ur@yhIfVlOUZThGI+#c$sOqiX1g*$yZzPmt^-=Ijk*C6bmm^qZ^k~b~*#Ydi5 zhla$H&r|}^%zJhlK(+1Jl~tWOzPa#z%%Fsw&66{)j2YCV@)%k~X3b^iiNAQZcl!>o zbcI*K1_e?F4T>U+09)wLZm6fzZv`bZs8wc@v!l!Auz@3y6FZ++|C$1_iyyy|@89s=61KBb(Uw;yNx8=cCFz z>MaJr4fNNV^MrLIqN!*q7xVGP{K8p5sOSvL2EiACYtSHcQzY*I%H17CgW6)?$0ELL zs?KUqoY!5nF)$M>77tUTa2zxYB*5e8FaQ`&Ff|0EK`fbw^RB`~+Qt1ZtptDS_)FfB zK%Dm!9(3hJd{|uj+b@f{4B#=)Xm!=gMlKrX-O6mvh=b};&#pz>`*LYnW1eC!3`A?2 z42lh^5+8fH7qSB-uZ*CUV^_SgkwJ~(Q@{JFw8@|bPz*-^6bHhQotk=IDCw(+izj}s z=aO;Wu8e|4#hO>kQLkuyb*ZNZWNolE2|i-Cd{^*A)y_dpy?wCY81x#{`b5H&<5luS z#TT?r3H`=;fMwtW@?(_yb;)1Q|_ zM_Ps*zRjRGC(Q;W3hT>jL#ycfgC0^5cl^PGP@nk4A5|4?29OvJIACzCk4Cn+BK4XH zSn@_7L|yyZO&dLN-cneG9eJ_x>4hC2&Z(&42s>PZAkq3k@Du9wg9ARj$iBY0Asy$9 zg*Q`rAwk6_5KIBvRo+ceSIlJmv_Iu6{W;aUhl7p>Zzr) zxrcq8l&r4_y@qCflKIRJ_|yT>`^FCB7axD4Y*{its!K1}C?;Dc7bGeE{SE3vmA|>9 zbSTaT3zYixN`M_4q^JCul9=HytNXA#qzh8LFLxBn^z3R`FNqv5xwF= zx$}MI#GBtPT`{7sjkyv@pBch-lU`VR&cK?QoekQ7zUKx9p|eGWMqH~G;GM`$#CSS+B8U`w3Uk$ zTLH!^t5-Xe&Z%dbIriK;OVAusobM@gWff2V=`0B2)bFmTaK`zz!UMBjNepxk%6>Hn zV^B=Kn`AolV)&}XSH0JUf}-WU6^0JE|GMr$wOJWy0zz692(icczCx$%um%u>*7r>= zb!EFAglUPm`RiHY@87H4or&|lkqPYV(kqxTtXGU>T(7un*0f?xTJ*Ac2jV&vGWkZm z&^!pNX@Zs%8LRX{D=qJk5#16Hnn0k#zLX1eVpI(=k89~Sq2RHDts`BZ@?&3WbQgT<#<-wHE^IZd%%o#G!qYOwd|Yh^SO1V}TFk9X<^ zTJ<~SLcuraP)w&I&L;}p?@+Xn_gR$gQx*jaA{L0m0v~T(8nHm?5u#+(_U(l+p~hz! z*dadgadC+aj9;O*ghlbY0^(;MZv_`c{Yf86i06N@9Bkf=pZx7CeVk7h8b|YulNKQ| z0yA@26lXaEWvaOzix8#tY4O=lpF$z=@;{#k_%rh_{on`r{$egpS|AkBXvQc^rX#Sn zgAu_cKJ}NK;7(P3_As)DZ+y0|%oyjxg(3a<*?C6<|FH``A3}4iV6h61z2ZN=NPr&{ zJlP0o;QLON^$%GTiHunkBruK0F%loPDE4d6BDg4&j#z|tO0(M{bkS=(tw?T5S_uMk z7GaRi#bi+|w^OV*l>`{O`jiockG+2CQv~?)tJ8kiG0^<=@?y6|?WR!{jhEzPuOR}t zqv!ux39;Dce|-!bwtN0o0nyouf7{3gA;^W$go^kl`3zn5=y zSOnXMY$0v(UBN=~2H^cjtsg>oUSGI1(}140Sb(*j5KXz4iog1M4>BAp`v-?03@ZQM zJ1a64$gF@6)5HVskPC{{1lS9B>n!3Y|GNUBv1wne28T!Yl@{5LxxZ>)*babsN_ClW zMB%IvmWFc9n&+rleDzc05}16ulzfAhPCEnpdmJbRlyZ|?jzXJFN=21L zC|HS6P$uvsqW+4(P}J=Tfsu^5#hd<93!cl%|EVkLu?VA<@$p`9{&&l@-LNz=QMIvx z1K1C2u+&bNc)rb|B%o++s98Mcy9$7(-QSfmeHJpi2>HZ+f7giGj&Darh-tQv$In0` z(h~^%7NJ(M52GWfk8EJk1x%-fbk0Jx%nl1Ve-^4N>9kNYUR~X9QL_9{5QJe;&Y`o= z4${M+8`j4yyb5ADYRn*O(Fip8lCtriux1g!o~Fs_hAJ3;HY!FfWa~7vd_}7zA4+zQ z$R6h~7^i~z`N+IEXffG04NVWGjQ`K#M2z2GoCGcZKZ_Hf^-~rnDwn4$PSQBJI4**w zqedoV6kqwzmNPs?!DSpDhm!89rFK4T{O`r^8-=uSaxuKd$;B9&xDQ15K8UU+^5&Yc zeAdom6!&~rf?{M=5%M!WBS{sZ?Z_(~FM`EFL(*esp#2OAla5(vEgB?yW+7Ec*r+Uv z-#BGiQsm)Tuq=b*)GTzvx`=V=vIKy-|7TgElhqAL^4M%tvNbWKYILM-4MYSzlL{6y zPGoA_@?yooR=X!`^$1<8O^e-A)~P8!T~MPOan3>I$V7I-iXtbuW)7-Ac7@X!h0|># zf*m*wCEzsKF&E81S>l_Ew8%jY&PD!3HlrY0v?+REAoJ#-2X%%i?B9_Vnn!AfTuECd z0ufWpmq_J!y^&TH^uzk(z>h*MW2zKEg_edNSqv|&b@kviZFk;njX&BF_cstm2?v-CY`#U@^Yp&_pVt?EXi0dIwN za^h{|{4z9(n#s3is0zTeatZnw8X>POK_3C#k1a*(H!IYPQJe(Y6w({?lL}m?Qca8l z&jmLq0=?#lW*G`pH@EPnf}**sa_W;0S(Q|Z$ExHQ)8yf0Xv0dEm69oQ0rK}DE1eom z+}*2kGJaT6lY*SHD$>{k&G${MOsu1-t6Es=Xwz0>SPyxncz>Lm6_% z3Ychyyto2AIxDNZ!CTxymRwzq{;(<4$Xg2?JFJSwW6L))SOuH4w#zqY%Es!glcPAS zG{=Gr58{7e=dKA(fSA5u;aLGLp7Rl0$!fK>KE^_W_Sdm^bx*FBN<@jo_+o49jx4^uf{K9sV&sU>2Q4e``4eAB`tl-hI zb?HXFyU?;TFHk$IiUIS7qR|BJGt<)@312Ma0(eZ32oJ~|A^UjbSrai&2yGM&uLz1> zK*Agt(rJ~`K#;0PnIWNOCd+HkUqOK8)uKBWJI(brKlq)AfGZRj^$B*fEU#|zN-Zib z&6y$H?umMR-CbrwG#raY=!Uq7d|QjQPIsDRwIo+wi`J5TI&=oftVQ*UOfU$Ddm>Wn z_0d=DR6#6E@nvPlKD6aCcyYY%JDB9xO}(xMR-WhCQ9 z9g@qAAfv{o|y1pUuUwJ|4~(nu3;$x%~k$rbdj)m&e|)>a89 z{4rW#R+=Z8c&pr8m4+-BO|W=Us(LH=-~dX`8dll_n)t4qoDtm`%n7P92f$Yak9FtR zx?L*1BPT?BnF>V>)j>#o4F(`2ZvS4jO08NGB*zb;cTgv}nxH0_&zl5oE$he?P?6>g z3smkP+H0UyJIP#w%<#^W*MRkCCx5;MT?N*1|MjR?yzjs?Las#**X*DR3CodpDOwbw zqfLDLzb6(aiK zQb9U{I^aNo0lhg|HAdzq*k$Bjx1yz>lEp`m1JrK#2(p68KYRpr&g@p2!MHtS(QRNj z+HOZPi0?M^F=`{0+tF@zG^h5FcW*|Ei@=ss*E&j0-HvWWRg!oIV$jn59H99?D6&(w zP;xFb5gCX=9=D%7a3}f=jGMU&X_hu7K%7zumj?`Kt{zHq3?j6_J$d9Vv}4Yna59l9Io5I7pYy$9(5Z07wGHLz_tbvLp96m>!U?LS3rn|kOxYSW1jY>(69 zUuso@N;9B^bbv}l_e3X0pJ+$1vXQ9oMVp|HbMHkwu;hw+0lOQ?>3dOp*#w2%kdOrP zOD$PmUmFtyox?4h0g3B)+Ly@HMacE{p)=vdC+|b&E}<_5Jak29YJ18Ch+e9{AI+&j zR;l$ZkfAwE3Bj5e$Ru|1t&j~Owr^1Nf~LeyNa$MwnlWQwjo^+9G?9zH0S~W<{Nfw% z6Pu*BzCnlB*?lHH8L9PzqDi07df-Avg*p%1$y97>O3-?6H396L^fux1CR`bz%})!| ziAl)Sa0gi)pAe8ssp#Nf$R~uzG{k&8FX9Aw?DxY@lHNt7c?`3ZMSh8&#ymQ6u?s5m zoY+O32osogIDX?ixPW;N?U3GIz(f&BOI?eYE(Ykmt%UhKM*SqWgxP_brB|0Qk0Z9t zq}GtR%bBIpgUgtUry-A2x03lC2Vgc^!`y)3dJoTBglBd`JK0k)MM&iqrdaCKG8eL7 zGXA)MxfAtEdp0tqn4}C0FFm!HIXZ)DrY{gzJ5wUa#Wu%$;CF+#; z7G`V?(0OMM^V}@P=OjrFa}1@VRbJ+wEQ&}K0p|Hx3x=G0%vlJHhMa3+(zj_Q#3FD% zMwz{w*U5*H&jqYXi7`dcJ_cIU(hosX7m{NxEILQh+r;2NsIn#i- zYY#Kq5v)k;M&{-sWS17)$rur8mwNAFS~>FO{m@jpkEucsuV^1*%A|kX&z#51%sP3W z-w!GAI)`-LgG>WP9nxdR84Ch1ohdP!kc}85=5i>#A~7*|sDGGQMIZk1=S&&7;O9)8 za<4)*>D`|*JqR1%^y&SFOG(Eg%#V9?6QU5?M*VUi4Lk(}=_ zwG_c9;pFc3nQqj6@B^kATCDnj`3aiS=$s-hP2`s!G9MA+C(7(SphGJ^W)>l~3R)5C zr%W|*eZo8pPS}!9nXRPg6f>V}e~c-mB)kn>p8aR$ZVY%a{R?I*T#R2ZB3xekg1Hco z7${jz#!fOoze zW)}I&x6H;FZO*)zY?d_tX2uX|mTvnG0}f!HQ_%U&;#=uuJsdy8; zT8tm@(BmlvuK?$T$agca4wOze6Tim*>@S>ybD(oq%)z4!R2R*MlhMmkcAB_=Eon&I zMjBjp#RveTYwD6xY5|^&P>5Wx5In2C#f@pZpmK#E6##pCaO+Oi=1Vz`aBgkYnBTtZT;2;$ToW2qllVCaSV#4-=ujB3HyK;OJJ7@>mZmqzp zHks&L(y3H5qT-$OK3O|av;$QzF()M^1CORmEK5)&UOnG2Nw?j;t7{J&N^q3AJD_q zAKk?Vf#hT;;Pq9FRV84YMz2!N7dzl_(3kT@Gm(5vJJf_Jfe=CX68XxmiOP`AH8RoF zFjS_ZY0nTGWK5*0$ZuESX}jYSmEmZiVar4vsMiD;P!)>?=nzxJZknu3$MP+!pblJv zL1hqS5~ENTin^4}s>b48m{>2=c|sOhs9>h~+8$usB*TZIAk%ORcLXG>8=lCFQStp# z8r8rKO}IwJfi`lZ8ZRzu{;#JI@DEjdA9+QMOBc1mHVv*p5wc5z%Ssc_YJi%E8h5fr*(-+}V1V4K!D>+TKWK0> z0;|7b4c>>q3%HNRn=vf>Cu{LGFjUoAe1H_`@GSC-7V81|&(Pu3U@|u7@F50S$cgoM zCF9GG|6Px(!8KjJ0dT32ys!bQ0CF$d0Bu{z(G9qae6ayfCnq-GWn|t)xcfWYf#sOL z5%+>w3v9$mK()6wQcMYw&(6Z6eRsrReod zJoiF{CnO6EG^iR&lD8Uh)9fKwjUk_+YcaCcfHyNO33B=hyiq!Az%wvF&|OVn6Xr_3_1Tk;8}4CUJ7PZ*MdJpfTJa?_zNa2KU*S|x8W5G@{?^=05TWh+F=QXq_%c^ zD}ruL*{}qcJ38>aaDfgMkw7Q#-b*g(#7{w0YZpvic_P48Q3&EddiuF7p>9bisVh5J zGB0FrsXoCjJB@6`uuqC^6}nig)DiWi7Mi3$lKz zcmo-B;Oz_aP+J>ujksNj8n^)Bkw>APHD-kP!EU^SwZaWqLGR4;ZfiHLV1xATT)11+ z0e9fNHmE`XpEXL?b*cni(0qw+r;YFE#^vBqJ=2Y2>~KaHBl`~HN^*7&KEQM->rK?X z_%5a^LyqnRaNm6dEaY&8>>UM>pVxC^^mrp}mwL$3|NOr4n|+;}~>v$wl(kShBEAI?zn(dD?5+~ULc z0Fmu}Y-R^DEi@m4JVbz{50kPgtnz|8xzlvC)VQSxcu$k5w(KDqX7JV#3H!+r!nzvnfm22K@|Y5Q>t{R7u2M#Co#4pOhcKtC)0_Jn$nB+JqE6fEOS(l;yqBg$MBru$<&df~{zX zyhHGg;#d|oH(iiQtr<8wQ?qk5-Hi?t>ou@WgXD^9V2MXa@wK>l(NLCdi^-c9jnODL zKM{49#ID69&<-w($o1FaG2|wt*I^BcN&4&XajH*W9l|{*N7`?|B_%n^@*J?7+I8q7 z6#(%!k?kpdR z#$a#G5RLc)gUW$U-XV?O4AdYS5pThoSq4~mc|pKsl%Bl>2M{FD08OqNg&*q?kYPu5 z63JlTy+`l^GXOgp?#CZPFH-eU{OU9y&|J1Bgw30zb(hll0uL@at%f z1x5i{z`;wXzz2QcX_%&wPSdGOv+&GH-g+9QX(r6C@n_&MocuLzgpgJD8~owScIZU$ z@w-UHGuXwLvofqQ7SOUIcq!8{mC~T}&NKK^#IC}7~^msC4f@x58{-WwpT8(+e6!L2d9gsZB% zfvPEHxPNL91cD~CRD7GX?;w?7NvoRUR@qXOL| z^hXH%ddV;Ui2GoelGk9J8zoSdbJ6^&tl~ROI8CGE^y^r?yBUT}x!mBjDom2^jY*k3 zeUv?|GF{aFlq0!Ln7iUP8L3;fLn+FBlU(6*OyGlDOy!G!4{|vITavBX7M8c3b;4R*}+o6evlNuD9_5a`J7gVNwnSbZg(i=Rw>n-TNnC6(HW6_i)Ml zfCIiW1yr5jm`mFD9snU5aqvkJd>@mg9-sz%sZaHOYJ&B9w$F<5%o*^s*;BZoXob34m8#lCQl`~6?S|Li$EG5(O zW4=lG!bbj{z!I(qL@FA!8rZvbrG*yq&o5wMn#h`y0Mx@IcoLUs`}5tmO?abt>?$mk)+G-tE-&& z`oo?-o!K}jw9w{u@~>04vBIVoEHG@XXULV%r+7pDkm~55^5veM419@~m0RHGrgAvj zDBr=60GMhM9Np`M!`XUr@Jn1?X3Og@zv88?Vv}3Qtok( z`BVTtlV8jGtP=Se&jzQ+N^buaS3&FB9tOS9Q!M{teM8%2*&t@X>3_h!YZuW zsD*UY=hN7gkXt76ir9NVD<3UlcS4&bXRsfDk^c|9z{B@vvIZt;g~OwZr21Lx67UbC zp4sfUc_SGMf?;JJLL6}F;jyB@dT zgeyq;s>SSP@G#F^%%)(E?0*)sZ%=QfCmQ%>^7As*v^!x|dJdSCUG%6~86jy_&Puz? z%1EtIWuy?arz3gH%1CLm&`(+FH_IdPUbC_bpYaP`Gi9;7{|!7IGEW}wicQ=HFnpUp z_AFs_;7dKYgbhs_F$7hv%d1g85o<3w&$IR*^(XL<{0K{S|*gcRkJiUT-L8tJY)S%Q?&i<>Drpe=M9fPvu ziX?kCI3#m=xy7U-#ZCt|t1rd=o~AYAI(|Pln{CAW<5`Rl{E7-*) za$W;+7&(3{2J}MykV3lngsrqv>E^$l!IA3(Jw{YrO-|g(Eh9JW;n)dT0eNG0!~Qfq zO<LA>GW?e2>_v{BY#pf49hIzLgAHMbtmgl3&Vt%B;!Vc^gSx%kE{G%ycuq znykK#?STOM{Oi~{h|3?njy(+9I|J9VSAjQr@_P1S@J2@tvFD?1^3OwTJ2f*mfCSq} z_y(2-47%)P&&dZA>bV+799&krnW#_ttdaW}DgscT%bFnj_H&D+Cym^XFk~N0Eu2Ub$FG|? z1KmDe-p;9UULvJ)Iyr9v`Y0Sq?{{%)Kwu>R!-e@L%gDAqZhYdE-}iB^o&mdJce=Sv z;7UvHdbm|!_f66ner{|Uq!Btsxl(c{#HpF6NsJ#XCU1r~4N4L&42+19^1w^rQ)df{&0baju+HoX>L7*HO*_$(0nhVD>=3#Jfy<$V9=1 z+&soDA=lJ%OQq}L+&2u9HWA%H0LptW=ZeTfDXw;QNIrC;?6LZ#Qz;I%+5OT_M!07f z$gg(fxHXU$7|(J0h;5YHGD|fn{cwnQlxv1e?lq%a=ZY}w+1JJ3haWOg-2?TWY&uA$ zk8wum$TG&QhBq=}T=`=84f-hweQUC9n%p1n5&#O zXyVf*K4;=hCO%_=G!e{7DllmfmH2!0PeOc z!N5uT0HX}_{@8+`BBOAs>%Y7v6~M1;KldnLiZn~)IEpO`uI6$~M_&4Gxti0G-UD0- z`SfalARA1KJh=-5bM*nh4Fl;rz_rZPPh~^9Nybp=*#lfTJpu9QHCz_8ORj6VRyZ|5 zj^4~wN-tf{9p{+g{215X%*~qBMTt~EAud6qpAz}+lm-+krZlM{S03Z81D9^iLxAI> zq~jrO-#JE@)MPKcbaCltS(8he$?-N5Z!q!U9Ph~S9usdi@l7G#XyQ9e{IH2{fuf18 mGV!A_G){#aD-n)!Vel*OInJ#FWBT%OPKSCW^fS&q?f(G$(Zq!S delta 19478 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 NWqw?$eUNj`_&>7kHAesd diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 60656bc6d6f3450acca6ca6d4369da29b4749527..9b92d1489c63f744f04a6085c653d32bd45b6331 100644 GIT binary patch delta 495 zcmZ8e!AiqG5JioMc<|JlpD>{4p@^rq)1-v3)DUW#V;Xv>RIB2__7V>{37+#2f+wLT zKfvGcOPtvRQoJlPZ)e|o^R{~HeZTi!tDS1M+WYc8@^XA(CCV7kZ=**eU_?uUc)|5& z?iFQx8&K{Hi4{@ltRyNm17>pYvooAh2R<6`n*X~J*^K5!2#xwmlyBwM><=0sfs%>JYv=S42XU*m3KR!e7Ag%jYLgCCHKy { + const search = new Event('search'); + quicksearch.value = ''; + await new Promise(f => setTimeout(f, 100)); + quicksearch.dispatchEvent(search); + }, { + passive: true + }) + } + } +} + export function initSearch(): void { for (const func of [initSearchBar]) { func(); } + initQuickSearch(); } diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index 8ef280397..d78e9e9b9 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -416,6 +416,27 @@ nav.search { } } +// Styles for the quicksearch and its clear button; +// Overrides input-group styles and adds transition effects +.quicksearch { + input[type="search"] { + border-radius: $border-radius !important; + } + + button { + margin-left: -32px !important; + z-index: 100 !important; + outline: none !important; + border-radius: $border-radius !important; + transition: visibility 0s, opacity 0.2s linear; + } + + button :hover { + opacity: 50%; + transition: visibility 0s, opacity 0.1s linear; + } +} + main.layout { display: flex; flex-wrap: nowrap; diff --git a/netbox/project-static/styles/overrides.scss b/netbox/project-static/styles/overrides.scss index 03c72c6e6..7fa366df8 100644 --- a/netbox/project-static/styles/overrides.scss +++ b/netbox/project-static/styles/overrides.scss @@ -34,3 +34,11 @@ a[type='button'] { .badge { font-size: $font-size-xs; } + +/* clears the 'X' in search inputs from webkit browsers */ +input[type='search']::-webkit-search-decoration, +input[type='search']::-webkit-search-cancel-button, +input[type='search']::-webkit-search-results-button, +input[type='search']::-webkit-search-results-decoration { + -webkit-appearance: none !important; +} diff --git a/netbox/project-static/styles/theme-dark.scss b/netbox/project-static/styles/theme-dark.scss index c0933e991..4bbe5cea5 100644 --- a/netbox/project-static/styles/theme-dark.scss +++ b/netbox/project-static/styles/theme-dark.scss @@ -92,6 +92,10 @@ $input-focus-color: $input-color; $input-placeholder-color: $gray-700; $input-plaintext-color: $body-color; +input { + color-scheme: dark; +} + $form-check-input-active-filter: brightness(90%); $form-check-input-bg: $input-bg; $form-check-input-border: 1px solid rgba(255, 255, 255, 0.25); diff --git a/netbox/project-static/styles/theme-light.scss b/netbox/project-static/styles/theme-light.scss index d417e1bf6..c9478f1cc 100644 --- a/netbox/project-static/styles/theme-light.scss +++ b/netbox/project-static/styles/theme-light.scss @@ -22,7 +22,6 @@ $theme-colors: ( 'danger': $danger, 'light': $light, 'dark': $dark, - // General-purpose palette 'blue': $blue-500, 'indigo': $indigo-500, @@ -36,7 +35,7 @@ $theme-colors: ( 'cyan': $cyan-500, 'gray': $gray-500, 'black': $black, - 'white': $white, + 'white': $white ); $light: $gray-200; diff --git a/netbox/project-static/styles/utilities.scss b/netbox/project-static/styles/utilities.scss index cd8ccc46b..a5a4bf038 100644 --- a/netbox/project-static/styles/utilities.scss +++ b/netbox/project-static/styles/utilities.scss @@ -42,3 +42,9 @@ table td { visibility: visible !important; } } + +// Hides the last child of an element +.hide-last-child :last-child { + visibility: hidden; + opacity: 0; +} diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index 22f6d8be5..79a9d6b6f 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -4,84 +4,85 @@ {% load static %} {% block content %} -
-
-
- -
+
+
+
+ +
-
-
- {% if request.user.is_authenticated %} - - {% endif %} - - -
+
+
+
+ {% if request.user.is_authenticated %} + + {% endif %} + + +
+
+
+ +
+ {% csrf_token %} + + +
+
+ {% include 'htmx/table.html' %}
- - {% csrf_token %} - - -
-
- {% include 'htmx/table.html' %} -
+
+
+ {% if perms.dcim.change_interface %} + + + + {% endif %} + {% if perms.dcim.delete_interface %} + + {% endif %}
- -
-
- {% if perms.dcim.change_interface %} - - - - {% endif %} - {% if perms.dcim.delete_interface %} - - {% endif %} -
- {% if perms.dcim.add_interface %} - - {% endif %} + {% if perms.dcim.add_interface %} + - + {% endif %} +
+ {% endblock %} {% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{{ block.super }} +{% table_config_form table %} +{% endblock modals %} \ No newline at end of file diff --git a/netbox/templates/inc/table_controls_htmx.html b/netbox/templates/inc/table_controls_htmx.html index ab8167bc0..099ad537e 100644 --- a/netbox/templates/inc/table_controls_htmx.html +++ b/netbox/templates/inc/table_controls_htmx.html @@ -2,31 +2,21 @@
-
- +
+ +
{% if request.user.is_authenticated and table_modal %} -
- -
+
+ +
{% endif %}
-
+
\ No newline at end of file From e2d53139403218ce47b46b8808580b174f1b3dc6 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 11 Aug 2022 13:02:37 -0400 Subject: [PATCH 16/34] Changelog for #9857 --- docs/release-notes/version-3.2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 598ee7874..e57cea3b3 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -6,6 +6,7 @@ * [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing * [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel +* [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields --- From 6a687a9ed1027e2819391076195138c7babf9400 Mon Sep 17 00:00:00 2001 From: Craig Pund Date: Thu, 11 Aug 2022 15:16:01 -0400 Subject: [PATCH 17/34] not necessary to prefetch --- netbox/dcim/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 28325bcfc..bd693e392 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1787,7 +1787,7 @@ class DeviceBulkDeleteView(generic.BulkDeleteView): class DeviceBulkRenameView(generic.BulkRenameView): - queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') + queryset = Device.objects.all() filterset = filtersets.DeviceFilterSet table = tables.DeviceTable From 5873ad95dc16db127298037b1af5093543583eff Mon Sep 17 00:00:00 2001 From: Craig Pund Date: Thu, 11 Aug 2022 15:16:42 -0400 Subject: [PATCH 18/34] handle objects without names --- netbox/netbox/views/generic/bulk_views.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index f17bc179d..e1f73b225 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -632,7 +632,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView): replace = form.cleaned_data['replace'] if form.cleaned_data['use_regex']: try: - obj.new_name = re.sub(find, replace, obj.name) + obj.new_name = re.sub(find, replace, obj.name or '') # Catch regex group reference errors except re.error: obj.new_name = obj.name @@ -676,9 +676,6 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView): else: form = self.form(initial={'pk': request.POST.getlist('pk')}) selected_objects = self.queryset.filter(pk__in=form.initial['pk']) - for object in selected_objects: - # Do something to raise error message to user - pass return render(request, self.template_name, { 'form': form, From 693ad700e83a49f8f9852bc4a952ed57811c10d4 Mon Sep 17 00:00:00 2001 From: Dorian Bourgeoisat <33571477+DorianXGH@users.noreply.github.com> Date: Fri, 12 Aug 2022 00:49:13 +0200 Subject: [PATCH 19/34] Swapping NG-PON2 as main name instead of TWDM-PON --- netbox/dcim/choices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index c91faf3da..1831ee8ae 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -818,7 +818,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_GPON = 'gpon' TYPE_XG_PON = 'xg-pon' TYPE_XGS_PON = 'xgs-pon' - TYPE_TWDM_PON = 'twdm-pon' + TYPE_NG_PON2 = 'ng-pon2' TYPE_EPON = 'epon' TYPE_10G_EPON = '10g-epon' @@ -964,7 +964,7 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gps)'), (TYPE_XG_PON, 'XG-PON (10 Gbps / 2.5 Gbps)'), (TYPE_XGS_PON, 'XGS-PON (10 Gbps)'), - (TYPE_TWDM_PON, 'TWDM-PON (NG-PON2) (4x10 Gbps)'), + (TYPE_NG_PON2, 'NG-PON2 (TWDM-PON) (4x10 Gbps)'), (TYPE_EPON, 'EPON (1 Gbps)'), (TYPE_10G_EPON, '10G-EPON (10 Gbps)'), ) From 41ad9b242c468fd3b868037320df7f4fd6123e0b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 Aug 2022 10:12:01 -0400 Subject: [PATCH 20/34] Fixes #9986: Workaround for upstream timezone data bug --- docs/release-notes/version-3.2.md | 4 ++++ requirements.txt | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index e57cea3b3..c572328bc 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -8,6 +8,10 @@ * [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel * [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields +### Bug Fixes + +* [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug + --- ## v3.2.8 (2022-08-08) diff --git a/requirements.txt b/requirements.txt index 59bd1e8cd..a227971fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,3 +34,6 @@ tzdata==2022.1 # Workaround for #7401 jsonschema==3.2.0 + +# Workaround for #9986 +pytz==2022.1 From e4fa8af47f7ae449bc490d927e5ac329a419e79b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 Aug 2022 10:48:16 -0400 Subject: [PATCH 21/34] Changelog for #8595 --- docs/release-notes/version-3.2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index c572328bc..824e99778 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -4,6 +4,7 @@ ### Enhancements +* [#8595](https://github.com/netbox-community/netbox/issues/8595) - Add PON interface types * [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing * [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel * [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields From ca0b21bef5723aedfffaacd90df32a41e965a1cb Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 Aug 2022 11:25:03 -0400 Subject: [PATCH 22/34] Closes #9980: Use standard table controls template for device interfaces list --- .../device/inc/interface_table_controls.html | 11 +++++++ netbox/templates/dcim/device/interfaces.html | 29 +------------------ netbox/templates/inc/table_controls_htmx.html | 26 ++++++++--------- 3 files changed, 25 insertions(+), 41 deletions(-) create mode 100644 netbox/templates/dcim/device/inc/interface_table_controls.html diff --git a/netbox/templates/dcim/device/inc/interface_table_controls.html b/netbox/templates/dcim/device/inc/interface_table_controls.html new file mode 100644 index 000000000..14e552439 --- /dev/null +++ b/netbox/templates/dcim/device/inc/interface_table_controls.html @@ -0,0 +1,11 @@ +{% extends 'inc/table_controls_htmx.html' %} + +{% block extra_table_controls %} + + +{% endblock extra_table_controls %} diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index 79a9d6b6f..94e38dbf3 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -4,34 +4,7 @@ {% load static %} {% block content %} -
-
-
- - -
-
-
-
- {% if request.user.is_authenticated %} - - {% endif %} - - -
-
-
+ {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %}
{% csrf_token %} diff --git a/netbox/templates/inc/table_controls_htmx.html b/netbox/templates/inc/table_controls_htmx.html index 099ad537e..6d6926d5d 100644 --- a/netbox/templates/inc/table_controls_htmx.html +++ b/netbox/templates/inc/table_controls_htmx.html @@ -1,22 +1,22 @@ {% load helpers %} -
-
-
+
+
+
- +
+ {% block extra_table_controls %}{% endblock %}
-
+
{% if request.user.is_authenticated and table_modal %} -
- -
+
+ +
{% endif %}
-
\ No newline at end of file +
From f942216f3f4931c0d1e835948478e976172713ab Mon Sep 17 00:00:00 2001 From: Christoph Schneider Date: Sat, 13 Aug 2022 13:54:38 +0200 Subject: [PATCH 23/34] re-enable markup in longtext custom columns --- netbox/netbox/tables/columns.py | 10 ++++++++++ netbox/netbox/tables/tables.py | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index cc20bdd0c..8b961219f 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -418,6 +418,14 @@ class CustomFieldColumn(tables.Column): """ Display custom fields in the appropriate format. """ + template_code = """ + {% if value %} + {{ value|markdown }} + {% else %} + — + {% endif %} + """ + def __init__(self, customfield, *args, **kwargs): self.customfield = customfield kwargs['accessor'] = Accessor(f'custom_field_data__{customfield.name}') @@ -445,6 +453,8 @@ class CustomFieldColumn(tables.Column): return mark_safe(', '.join( self._likify_item(obj) for obj in self.customfield.deserialize(value) )) + if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT: + return Template(self.template_code).render(Context({"value": value})) if value is not None: obj = self.customfield.deserialize(value) return mark_safe(self._likify_item(obj)) diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index d55038c4d..deb0d8b90 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -181,7 +181,8 @@ class NetBoxTable(BaseTable): content_type = ContentType.objects.get_for_model(self._meta.model) custom_fields = CustomField.objects.filter(content_types=content_type) extra_columns.extend([ - (f'cf_{cf.name}', columns.CustomFieldMarkdownColumn(cf) if cf.type == CustomFieldTypeChoices.TYPE_LONGTEXT else columns.CustomFieldColumn(cf)) for cf in custom_fields + # (f'cf_{cf.name}', columns.CustomFieldMarkdownColumn(cf) if cf.type == CustomFieldTypeChoices.TYPE_LONGTEXT else columns.CustomFieldColumn(cf)) for cf in custom_fields + (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields ]) custom_links = CustomLink.objects.filter(content_type=content_type, enabled=True) extra_columns.extend([ From 6f09d94e2a99330ecd09e7d3ee0600fba8386716 Mon Sep 17 00:00:00 2001 From: Christoph Schneider Date: Sat, 13 Aug 2022 13:56:51 +0200 Subject: [PATCH 24/34] remove commented line --- netbox/netbox/tables/tables.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index deb0d8b90..63aae1d19 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -181,7 +181,6 @@ class NetBoxTable(BaseTable): content_type = ContentType.objects.get_for_model(self._meta.model) custom_fields = CustomField.objects.filter(content_types=content_type) extra_columns.extend([ - # (f'cf_{cf.name}', columns.CustomFieldMarkdownColumn(cf) if cf.type == CustomFieldTypeChoices.TYPE_LONGTEXT else columns.CustomFieldColumn(cf)) for cf in custom_fields (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields ]) custom_links = CustomLink.objects.filter(content_type=content_type, enabled=True) From ac540b6183bd5bf8db57b1941de24aafc7b04f3c Mon Sep 17 00:00:00 2001 From: Christoph Schneider Date: Sat, 13 Aug 2022 13:59:19 +0200 Subject: [PATCH 25/34] remove import --- netbox/netbox/tables/tables.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 63aae1d19..8c5fb039c 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -7,7 +7,6 @@ from django.db.models.fields.related import RelatedField from django_tables2.data import TableQuerysetData from extras.models import CustomField, CustomLink -from extras.choices import CustomFieldTypeChoices from netbox.tables import columns from utilities.paginator import EnhancedPaginator, get_paginate_count From 36491b13d86b26a0e75d84b8b5db64403f3fbe89 Mon Sep 17 00:00:00 2001 From: Christoph Schneider Date: Sat, 13 Aug 2022 14:01:07 +0200 Subject: [PATCH 26/34] remove class definition --- netbox/netbox/tables/columns.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 8b961219f..4918dd3b9 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -559,29 +559,4 @@ class MarkdownColumn(tables.TemplateColumn): ) def value(self, value): - return value - - -class CustomFieldMarkdownColumn(tables.TemplateColumn): - """ - Render a Markdown string in a longtext custom column. - """ - template_code = """ - {% if value %} - {{ value|markdown }} - {% else %} - — - {% endif %} - """ - - def __init__(self, customfield, *args, **kwargs): - self.customfield = customfield - kwargs['accessor'] = Accessor(f'custom_field_data__{customfield.name}') - kwargs['template_code'] = self.template_code - if 'verbose_name' not in kwargs: - kwargs['verbose_name'] = customfield.label or customfield.name - - super().__init__(*args, **kwargs) - - def value(self, value): - return value + return value \ No newline at end of file From 15f4b1fd5daa5c8dd4338352d37049b47e4bf7de Mon Sep 17 00:00:00 2001 From: Christoph Schneider Date: Sat, 13 Aug 2022 14:02:26 +0200 Subject: [PATCH 27/34] add newline --- netbox/netbox/tables/columns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 4918dd3b9..113c8416b 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -559,4 +559,4 @@ class MarkdownColumn(tables.TemplateColumn): ) def value(self, value): - return value \ No newline at end of file + return value From 30ab1e5a5e1f12c3e88b145c511741f75b5d460b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 Aug 2022 09:14:19 -0400 Subject: [PATCH 28/34] Changelog for #8723, #9505, #9979 --- docs/release-notes/version-3.2.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 824e99778..f515e4d35 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -5,12 +5,15 @@ ### Enhancements * [#8595](https://github.com/netbox-community/netbox/issues/8595) - Add PON interface types +* [#8723](https://github.com/netbox-community/netbox/issues/8723) - Enable bulk renaming of devices * [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing +* [#9505](https://github.com/netbox-community/netbox/issues/9505) - Display extra addressing details for IPv4 prefixes * [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel * [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields ### Bug Fixes +* [#9979](https://github.com/netbox-community/netbox/issues/9979) - Fix Markdown rendering for custom fields in table columns * [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug --- From 0ef1bc8490dc2f6d6623a3b209406d57bc278efd Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 Aug 2022 09:49:51 -0400 Subject: [PATCH 29/34] Clean up bulk edit buttons --- .../templates/dcim/device/consoleports.html | 54 ++++++++++--------- .../dcim/device/consoleserverports.html | 54 ++++++++++--------- netbox/templates/dcim/device/devicebays.html | 44 +++++++-------- netbox/templates/dcim/device/frontports.html | 54 ++++++++++--------- netbox/templates/dcim/device/interfaces.html | 51 ++++++++++-------- netbox/templates/dcim/device/inventory.html | 44 +++++++-------- netbox/templates/dcim/device/modulebays.html | 44 +++++++-------- .../templates/dcim/device/poweroutlets.html | 54 ++++++++++--------- netbox/templates/dcim/device/powerports.html | 54 ++++++++++--------- netbox/templates/dcim/device/rearports.html | 54 ++++++++++--------- netbox/templates/dcim/device_list.html | 16 ++++-- .../virtualmachine/interfaces.html | 43 +++++++-------- 12 files changed, 311 insertions(+), 255 deletions(-) diff --git a/netbox/templates/dcim/device/consoleports.html b/netbox/templates/dcim/device/consoleports.html index afc306bd4..6f8b383c3 100644 --- a/netbox/templates/dcim/device/consoleports.html +++ b/netbox/templates/dcim/device/consoleports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_consoleport %} - - - - {% endif %} - {% if perms.dcim.delete_consoleport %} - - {% endif %} -
- {% if perms.dcim.add_consoleport %} - +
+ {% if perms.dcim.change_consoleport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_consoleport %} + + {% endif %} + {% if perms.dcim.change_consoleport %} + + {% endif %} +
+
+ {% if perms.dcim.add_consoleport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/consoleserverports.html b/netbox/templates/dcim/device/consoleserverports.html index 5f244cdc7..f246d4a82 100644 --- a/netbox/templates/dcim/device/consoleserverports.html +++ b/netbox/templates/dcim/device/consoleserverports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_consoleserverport %} - - - - {% endif %} - {% if perms.dcim.delete_consoleserverport %} - - {% endif %} -
- {% if perms.dcim.add_consoleserverport %} - +
+ {% if perms.dcim.change_consoleserverport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_consoleserverport %} + + {% endif %} + {% if perms.dcim.change_consoleserverport %} + + {% endif %} +
+
+ {% if perms.dcim.add_consoleserverport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/devicebays.html b/netbox/templates/dcim/device/devicebays.html index 5e33bdae0..d84408962 100644 --- a/netbox/templates/dcim/device/devicebays.html +++ b/netbox/templates/dcim/device/devicebays.html @@ -16,28 +16,30 @@
-
- {% if perms.dcim.change_devicebay %} - - - {% endif %} - {% if perms.dcim.delete_devicebay %} - - {% endif %} -
- {% if perms.dcim.add_devicebay %} - +
+ {% if perms.dcim.change_devicebay %} +
+ + +
{% endif %} + {% if perms.dcim.delete_devicebay %} + + {% endif %} +
+ {% if perms.dcim.add_devicebay %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/frontports.html b/netbox/templates/dcim/device/frontports.html index 0d0f9577c..513d02090 100644 --- a/netbox/templates/dcim/device/frontports.html +++ b/netbox/templates/dcim/device/frontports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_frontport %} - - - - {% endif %} - {% if perms.dcim.delete_frontport %} - - {% endif %} -
- {% if perms.dcim.add_frontport %} - +
+ {% if perms.dcim.change_frontport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_frontport %} + + {% endif %} + {% if perms.dcim.change_frontport %} + + {% endif %} +
+
+ {% if perms.dcim.add_frontport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index 94e38dbf3..2019d9135 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -9,7 +9,6 @@
{% csrf_token %} -
{% include 'htmx/table.html' %} @@ -19,29 +18,35 @@
{% if perms.dcim.change_interface %} - - - - {% endif %} - {% if perms.dcim.delete_interface %} - +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_interface %} + + {% endif %} + {% if perms.dcim.change_interface %} + + {% endif %} +
{% if perms.dcim.add_interface %}
diff --git a/netbox/templates/dcim/device/inventory.html b/netbox/templates/dcim/device/inventory.html index 18a0712f3..8b74acaae 100644 --- a/netbox/templates/dcim/device/inventory.html +++ b/netbox/templates/dcim/device/inventory.html @@ -16,28 +16,30 @@
-
- {% if perms.dcim.change_inventoryitem %} - - - {% endif %} - {% if perms.dcim.delete_inventoryitem %} - - {% endif %} -
- {% if perms.dcim.add_inventoryitem %} - +
+ {% if perms.dcim.change_inventoryitem %} +
+ + +
{% endif %} + {% if perms.dcim.delete_inventoryitem %} + + {% endif %} +
+ {% if perms.dcim.add_inventoryitem %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/modulebays.html b/netbox/templates/dcim/device/modulebays.html index fc1c9a60d..67b9d88d4 100644 --- a/netbox/templates/dcim/device/modulebays.html +++ b/netbox/templates/dcim/device/modulebays.html @@ -16,28 +16,30 @@
-
- {% if perms.dcim.change_modulebay %} - - - {% endif %} - {% if perms.dcim.delete_modulebay %} - - {% endif %} -
- {% if perms.dcim.add_modulebay %} - +
+ {% if perms.dcim.change_modulebay %} +
+ + +
{% endif %} + {% if perms.dcim.delete_modulebay %} + + {% endif %} +
+ {% if perms.dcim.add_modulebay %} + + {% endif %}
{% table_config_form table %} diff --git a/netbox/templates/dcim/device/poweroutlets.html b/netbox/templates/dcim/device/poweroutlets.html index d312fbbd0..61c2b61f4 100644 --- a/netbox/templates/dcim/device/poweroutlets.html +++ b/netbox/templates/dcim/device/poweroutlets.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_powerport %} - - - - {% endif %} - {% if perms.dcim.delete_poweroutlet %} - - {% endif %} -
- {% if perms.dcim.add_poweroutlet %} - +
+ {% if perms.dcim.change_powerport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_poweroutlet %} + + {% endif %} + {% if perms.dcim.change_powerport %} + + {% endif %} +
+
+ {% if perms.dcim.add_poweroutlet %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/powerports.html b/netbox/templates/dcim/device/powerports.html index cf71e81ba..cd8597e63 100644 --- a/netbox/templates/dcim/device/powerports.html +++ b/netbox/templates/dcim/device/powerports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_powerport %} - - - - {% endif %} - {% if perms.dcim.delete_powerport %} - - {% endif %} -
- {% if perms.dcim.add_powerport %} - +
+ {% if perms.dcim.change_powerport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_powerport %} + + {% endif %} + {% if perms.dcim.change_powerport %} + + {% endif %} +
+
+ {% if perms.dcim.add_powerport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/rearports.html b/netbox/templates/dcim/device/rearports.html index 73341990f..b370de189 100644 --- a/netbox/templates/dcim/device/rearports.html +++ b/netbox/templates/dcim/device/rearports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_rearport %} - - - - {% endif %} - {% if perms.dcim.delete_rearport %} - - {% endif %} -
- {% if perms.dcim.add_rearport %} - +
+ {% if perms.dcim.change_rearport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_rearport %} + + {% endif %} + {% if perms.dcim.change_rearport %} + + {% endif %} +
+
+ {% if perms.dcim.add_rearport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html index a576a1f9e..50af50525 100644 --- a/netbox/templates/dcim/device_list.html +++ b/netbox/templates/dcim/device_list.html @@ -1,10 +1,8 @@ {% extends 'generic/object_list.html' %} +{% load buttons %} {% block bulk_buttons %} {% if perms.dcim.change_device %} - {% endif %} - {{ block.super }} + {% if 'bulk_edit' in actions %} +
+ {% bulk_edit_button model query_params=request.GET %} + +
+ {% endif %} + {% if 'bulk_delete' in actions %} + {% bulk_delete_button model query_params=request.GET %} + {% endif %} {% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine/interfaces.html b/netbox/templates/virtualization/virtualmachine/interfaces.html index e3ffb84d4..eff98cdd6 100644 --- a/netbox/templates/virtualization/virtualmachine/interfaces.html +++ b/netbox/templates/virtualization/virtualmachine/interfaces.html @@ -15,27 +15,28 @@
- {% if perms.virtualization.change_vminterface %} - - - {% endif %} - {% if perms.virtualization.delete_vminterface %} - - {% endif %} - {% if perms.virtualization.add_vminterface %} - - {% endif %} -
+ {% if perms.virtualization.change_vminterface %} +
+ + +
+ {% endif %} + {% if perms.virtualization.delete_vminterface %} + + {% endif %} + {% if perms.virtualization.add_vminterface %} + + {% endif %}
{% endblock content %} From dedee0f9d9d3abded2a6665aea43283be7cc1a8b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 Aug 2022 09:53:13 -0400 Subject: [PATCH 30/34] #9979: Fix fallback to default value --- netbox/netbox/tables/columns.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 113c8416b..6ab50d4c2 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -14,6 +14,7 @@ from django_tables2.columns import library from django_tables2.utils import Accessor from extras.choices import CustomFieldTypeChoices +from utilities.templatetags.builtins.filters import render_markdown from utilities.utils import content_type_identifier, content_type_name, get_viewname __all__ = ( @@ -418,14 +419,6 @@ class CustomFieldColumn(tables.Column): """ Display custom fields in the appropriate format. """ - template_code = """ - {% if value %} - {{ value|markdown }} - {% else %} - — - {% endif %} - """ - def __init__(self, customfield, *args, **kwargs): self.customfield = customfield kwargs['accessor'] = Accessor(f'custom_field_data__{customfield.name}') @@ -435,7 +428,7 @@ class CustomFieldColumn(tables.Column): super().__init__(*args, **kwargs) @staticmethod - def _likify_item(item): + def _linkify_item(item): if hasattr(item, 'get_absolute_url'): return f'{escape(item)}' return escape(item) @@ -451,13 +444,13 @@ class CustomFieldColumn(tables.Column): return ', '.join(v for v in value) if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT: return mark_safe(', '.join( - self._likify_item(obj) for obj in self.customfield.deserialize(value) + self._linkify_item(obj) for obj in self.customfield.deserialize(value) )) - if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT: - return Template(self.template_code).render(Context({"value": value})) + if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT and value: + return render_markdown(value) if value is not None: obj = self.customfield.deserialize(value) - return mark_safe(self._likify_item(obj)) + return mark_safe(self._linkify_item(obj)) return self.default def value(self, value): From 6d328a82e9dac52821bba08841caa2e1188c310c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 Aug 2022 10:04:47 -0400 Subject: [PATCH 31/34] Cleanup for #9505 --- netbox/templates/ipam/prefix.html | 95 +++++++++++++------------------ 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index ed2c3339b..b15aa60bb 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -144,15 +144,14 @@ + {% if object.prefix.version == 4 %} + + {% endif %}
- {% if object.prefix.version == 4 %} - - {% endif %}
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} @@ -168,54 +167,40 @@ {% plugin_full_width_page object %} -{% if object.prefix.version == 4 %} -