From d9e7e8c39b59d7022df7c86774a37c50c1dc1d8b Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Tue, 28 May 2024 17:41:58 +0200 Subject: [PATCH 01/24] [#2503] Modified fonts & line-height for use with NLDS dyslexia-switcher --- package-lock.json | 2 +- .../components/accessibility/change_font.js | 39 +++++--- .../components/accessibility/enlarge_font.js | 91 +++++++++++++++++-- .../AccessibilityHeader.scss | 7 ++ .../scss/components/Header/Header.scss | 3 +- .../components/Header/PrimaryNavigation.scss | 6 +- .../scss/components/Typography/H1.scss | 3 + .../scss/components/Typography/H2.scss | 3 + .../scss/components/Typography/H3.scss | 6 ++ .../scss/components/Typography/H4.scss | 8 +- src/open_inwoner/scss/views/App.scss | 14 ++- 11 files changed, 149 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5d2bed807..782340f1e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@emotion/styled": "^11.3.0", "@fortawesome/fontawesome-free": "^6.4.2", "@joeattardi/emoji-button": "^4.6.4", - "@open-inwoner/design-tokens": "^0.0.5-alpha.5", + "@open-inwoner/design-tokens": "^0.0.3-alpha.0", "@tarekraafat/autocomplete.js": "^10.2.6", "bem.js": "^1.0.10", "emojibase-data": "^7.0.1", diff --git a/src/open_inwoner/js/components/accessibility/change_font.js b/src/open_inwoner/js/components/accessibility/change_font.js index 0e12ade47e..3399782fb9 100644 --- a/src/open_inwoner/js/components/accessibility/change_font.js +++ b/src/open_inwoner/js/components/accessibility/change_font.js @@ -1,8 +1,5 @@ -const changeFontButtons = document.querySelectorAll( - '.accessibility--change-font' -) - -class ChangeFont { +export class ChangeFont { + static selector = '.accessibility--change-font' constructor(node) { this.node = node this.text = node.querySelector('.link__text') @@ -12,18 +9,36 @@ class ChangeFont { change(event) { event.preventDefault() let root = document.documentElement - const varName = '--font-family-body' + const bodyFontFamily = '--oip-typography-sans-serif-font-family' + const headingFontFamily = '--utrecht-heading-font-family' + // Start of legacy styles + const oipBodyFontFamily = '--font-family-body' + const oipHeadingFontFamily = '--font-family-heading' + // end of legacy styles + const openDyslexicFont = 'Open Dyslexic' + const defaultBodyFont = 'Body' + const defaultHeadingFont = 'Heading' - if (root.style.getPropertyValue(varName) === 'Open Dyslexic') { - root.style.setProperty(varName, 'Body') + if (root.style.getPropertyValue(bodyFontFamily) === openDyslexicFont) { + root.style.setProperty(bodyFontFamily, defaultBodyFont) + root.style.setProperty(headingFontFamily, defaultHeadingFont) + root.style.setProperty(oipBodyFontFamily, defaultBodyFont) + root.style.setProperty(oipHeadingFontFamily, defaultHeadingFont) this.text.innerText = this.node.dataset.text } else { - root.style.setProperty(varName, 'Open Dyslexic') + root.style.setProperty(bodyFontFamily, openDyslexicFont) + root.style.setProperty(headingFontFamily, openDyslexicFont) + root.style.setProperty(oipBodyFontFamily, openDyslexicFont) + root.style.setProperty(oipHeadingFontFamily, openDyslexicFont) this.text.innerText = this.node.dataset.altText } } } -;[...changeFontButtons].forEach( - (changeFontButton) => new ChangeFont(changeFontButton) -) +/** + * Controls the toggling of Dyslexia font when button is clicked + */ + +document + .querySelectorAll(ChangeFont.selector) + .forEach((readmoreToggle) => new ChangeFont(readmoreToggle)) diff --git a/src/open_inwoner/js/components/accessibility/enlarge_font.js b/src/open_inwoner/js/components/accessibility/enlarge_font.js index b1d5b2bd45..1aeca392fc 100644 --- a/src/open_inwoner/js/components/accessibility/enlarge_font.js +++ b/src/open_inwoner/js/components/accessibility/enlarge_font.js @@ -1,29 +1,102 @@ -class EnlageFont { +export class EnlargeFont { + static selector = '.accessibility--enlarge-font' constructor(node) { this.node = node this.text = node.querySelector('.link__text') this.icon = node.querySelector('.material-icons') this.node.addEventListener('click', this.enlarge.bind(this)) + + // Set initial values for default styles + let root = document.documentElement + const bodyFontSizeDefault = '--utrecht-document-font-size' + const paragraphFontSizeDefault = '--utrecht-paragraph-font-size' + const paragraphFontSizeSmall = '--utrecht-paragraph-small-font-size' + const headingThreeFontSize = '--utrecht-heading-3-font-size' + const headingFourFontSize = '--utrecht-heading-4-font-size' + const oipBodyFontSizeDefault = '--font-size-body' + const oipBodyFontSizeSmall = '--font-size-body-small' + const oipHeadingThreeFontSize = '--font-size-heading-3' + const oipHeadingFourFontSize = '--font-size-heading-4' + const oipCardHeadingFontSize = '--font-size-heading-card' + const baseSizeDefault = '16px' + const baseSizeSmall = '14px' + const baseHeadingThreeFontSize = '20px' + const baseHeadingFourFontSize = '16px' + const baseCardHeadingFontSize = '18px' + + root.style.setProperty(bodyFontSizeDefault, baseSizeDefault) + root.style.setProperty(paragraphFontSizeDefault, baseSizeDefault) + root.style.setProperty(paragraphFontSizeSmall, baseSizeSmall) + root.style.setProperty(headingThreeFontSize, baseHeadingThreeFontSize) + root.style.setProperty(headingFourFontSize, baseHeadingFourFontSize) + root.style.setProperty(oipBodyFontSizeDefault, baseSizeDefault) + root.style.setProperty(oipBodyFontSizeSmall, baseSizeSmall) + root.style.setProperty(oipHeadingThreeFontSize, baseHeadingThreeFontSize) + root.style.setProperty(oipHeadingFourFontSize, baseHeadingFourFontSize) + root.style.setProperty(oipCardHeadingFontSize, baseCardHeadingFontSize) } enlarge(event) { event.preventDefault() let root = document.documentElement - const varName = '--font-size-body' - const baseSize = '16px' - const enlargeSize = '20px' + // NL design system styles + const bodyFontSizeDefault = '--utrecht-document-font-size' + const paragraphFontSizeDefault = '--utrecht-paragraph-font-size' + const paragraphFontSizeSmall = '--utrecht-paragraph-small-font-size' + const headingThreeFontSize = '--utrecht-heading-3-font-size' + const headingFourFontSize = '--utrecht-heading-4-font-size' + // Legacy styles + const oipBodyFontSizeDefault = '--font-size-body' + const oipBodyFontSizeSmall = '--font-size-body-small' + const oipHeadingThreeFontSize = '--font-size-heading-3' + const oipHeadingFourFontSize = '--font-size-heading-4' + const oipCardHeadingFontSize = '--font-size-heading-card' + // Set toggle variations + const baseSizeDefault = '16px' + const baseSizeSmall = '14px' + const enlargeSizeDefault = '20px' + const enlargeSizeSmall = '17px' + const baseHeadingThreeFontSize = '20px' + const baseHeadingFourFontSize = '16px' + const baseCardHeadingFontSize = '18px' + // Set all lower headings to H2 size + const enlargeHeadings = '22px' - if (root.style.getPropertyValue(varName) == enlargeSize) { - root.style.setProperty(varName, baseSize) + if ( + root.style.getPropertyValue(bodyFontSizeDefault) === enlargeSizeDefault + ) { + root.style.setProperty(bodyFontSizeDefault, baseSizeDefault) + root.style.setProperty(paragraphFontSizeDefault, baseSizeDefault) + root.style.setProperty(paragraphFontSizeSmall, baseSizeSmall) + root.style.setProperty(headingThreeFontSize, baseHeadingThreeFontSize) + root.style.setProperty(headingFourFontSize, baseHeadingFourFontSize) + root.style.setProperty(oipBodyFontSizeDefault, baseSizeDefault) + root.style.setProperty(oipBodyFontSizeSmall, baseSizeSmall) + root.style.setProperty(oipHeadingThreeFontSize, baseHeadingThreeFontSize) + root.style.setProperty(oipHeadingFourFontSize, baseHeadingFourFontSize) + root.style.setProperty(oipCardHeadingFontSize, baseCardHeadingFontSize) this.text.innerText = this.node.dataset.text this.icon.innerText = this.node.dataset.icon } else { - root.style.setProperty(varName, enlargeSize) + root.style.setProperty(bodyFontSizeDefault, enlargeSizeDefault) + root.style.setProperty(paragraphFontSizeDefault, enlargeSizeDefault) + root.style.setProperty(paragraphFontSizeSmall, enlargeSizeSmall) + root.style.setProperty(headingThreeFontSize, enlargeHeadings) + root.style.setProperty(headingFourFontSize, enlargeHeadings) + root.style.setProperty(oipBodyFontSizeDefault, enlargeSizeDefault) + root.style.setProperty(oipBodyFontSizeSmall, enlargeSizeSmall) + root.style.setProperty(oipHeadingThreeFontSize, enlargeHeadings) + root.style.setProperty(oipHeadingFourFontSize, enlargeHeadings) + root.style.setProperty(oipCardHeadingFontSize, enlargeHeadings) this.text.innerText = this.node.dataset.altText this.icon.innerText = this.node.dataset.altIcon } } } -const enlargeButtons = document.querySelectorAll('.accessibility--enlarge-font') -;[...enlargeButtons].forEach((enlargeButton) => new EnlageFont(enlargeButton)) +/** + * Controls the toggling of larger font-sizes of body-text and small texts when button is clicked + */ +document + .querySelectorAll(EnlargeFont.selector) + .forEach((enlargeButton) => new EnlargeFont(enlargeButton)) diff --git a/src/open_inwoner/scss/components/AccessibilityHeader/AccessibilityHeader.scss b/src/open_inwoner/scss/components/AccessibilityHeader/AccessibilityHeader.scss index 188acced8e..c29a674eec 100644 --- a/src/open_inwoner/scss/components/AccessibilityHeader/AccessibilityHeader.scss +++ b/src/open_inwoner/scss/components/AccessibilityHeader/AccessibilityHeader.scss @@ -20,6 +20,8 @@ &__list { display: flex; justify-content: space-between; + height: var(--row-height); + align-items: center; list-style: none; margin: 0; padding: 0; @@ -50,12 +52,17 @@ font-size: 0; } } + + &__text { + line-height: var(--font-line-height-body-medium); + } } .skip-link { position: absolute; top: -50px; transition: top 0.2s; + &:focus { position: relative; top: 0; diff --git a/src/open_inwoner/scss/components/Header/Header.scss b/src/open_inwoner/scss/components/Header/Header.scss index 28239e9cb9..c9ed8905ca 100644 --- a/src/open_inwoner/scss/components/Header/Header.scss +++ b/src/open_inwoner/scss/components/Header/Header.scss @@ -544,9 +544,10 @@ $vm: var(--spacing-large); .subpage-list { margin-top: var(--spacing-extra-large); - margin-bottom: var(--spacing-medium); + margin-bottom: var(--spacing-large); margin-left: var(--row-height-big); display: grid; + gap: var(--spacing-large); height: auto; overflow: initial; } diff --git a/src/open_inwoner/scss/components/Header/PrimaryNavigation.scss b/src/open_inwoner/scss/components/Header/PrimaryNavigation.scss index 361a28a1d9..a1f8f64aac 100644 --- a/src/open_inwoner/scss/components/Header/PrimaryNavigation.scss +++ b/src/open_inwoner/scss/components/Header/PrimaryNavigation.scss @@ -188,7 +188,7 @@ padding-bottom: var(--spacing-medium); display: grid; grid-template-columns: repeat(1, 1fr); - gap: var(--spacing-small); + gap: var(--spacing-large); width: 100%; @media (min-width: 768px) { @@ -212,6 +212,10 @@ font-size: var(--font-size-body); color: var(--font-color-body); } + + .link__text { + line-height: var(--font-line-height-body); + } } } diff --git a/src/open_inwoner/scss/components/Typography/H1.scss b/src/open_inwoner/scss/components/Typography/H1.scss index f2bcc7742e..07d937e36e 100644 --- a/src/open_inwoner/scss/components/Typography/H1.scss +++ b/src/open_inwoner/scss/components/Typography/H1.scss @@ -2,6 +2,9 @@ @import '~microscope-sass/lib/responsive'; .utrecht-heading-1 { + // Reverse font-family fallback order + font-family: var(--utrecht-heading-font-family), + var(--utrecht-heading-1-font-family); display: var(--oip-typography-heading-display); justify-content: var(--oip-typography-heading-justify-content); diff --git a/src/open_inwoner/scss/components/Typography/H2.scss b/src/open_inwoner/scss/components/Typography/H2.scss index 104193e7f0..8ff8c2186a 100644 --- a/src/open_inwoner/scss/components/Typography/H2.scss +++ b/src/open_inwoner/scss/components/Typography/H2.scss @@ -2,6 +2,9 @@ @import '~microscope-sass/lib/responsive'; .utrecht-heading-2 { + // Reverse font-family fallback order + font-family: var(--utrecht-heading-font-family), + var(--utrecht-heading-2-font-family); display: var(--oip-typography-heading-display); justify-content: var(--oip-typography-heading-justify-content); diff --git a/src/open_inwoner/scss/components/Typography/H3.scss b/src/open_inwoner/scss/components/Typography/H3.scss index bf45ca0a1c..7d59c8ea66 100644 --- a/src/open_inwoner/scss/components/Typography/H3.scss +++ b/src/open_inwoner/scss/components/Typography/H3.scss @@ -1,6 +1,12 @@ @import '~@utrecht/components/dist/heading-3/css/index.css'; @import '~microscope-sass/lib/responsive'; +.utrecht-heading-3 { + // Reverse font-family fallback order + font-family: var(--utrecht-heading-font-family), + var(--utrecht-heading-3-font-family); +} + .utrecht-paragraph + .utrecht-heading-3 { margin-top: var(--spacing-large); } diff --git a/src/open_inwoner/scss/components/Typography/H4.scss b/src/open_inwoner/scss/components/Typography/H4.scss index 3423ae5a1f..c78d337426 100644 --- a/src/open_inwoner/scss/components/Typography/H4.scss +++ b/src/open_inwoner/scss/components/Typography/H4.scss @@ -1,8 +1,14 @@ @import '~@utrecht/components/dist/heading-4/css/index.css'; @import '~microscope-sass/lib/responsive'; +.utrecht-heading-4 { + // Reverse font-family fallback order + font-family: var(--utrecht-heading-font-family), + var(--utrecht-heading-4-font-family); +} + /// -/// Legacy styling - other styling comes from NLDS +/// Legacy styling /// .h4 { diff --git a/src/open_inwoner/scss/views/App.scss b/src/open_inwoner/scss/views/App.scss index 9e715264f8..16f19f881b 100644 --- a/src/open_inwoner/scss/views/App.scss +++ b/src/open_inwoner/scss/views/App.scss @@ -201,43 +201,41 @@ --font-color-heading-1: var(--font-color-heading); --font-family-heading-1: var(--font-family-heading); --font-line-height-heading-1: 41px; - --font-size-heading-1: 32px; + --font-size-heading-1: var(--utrecht-heading-1-font-size); --font-weight-heading-1: var(--font-weight-heading); --font-color-heading-2: var(--font-color-heading); --font-family-heading-2: var(--font-family-heading); --font-line-height-heading-2: 34px; - --font-size-heading-2: 24px; + --font-size-heading-2: var(--utrecht-heading-2-font-size); --font-weight-heading-2: var(--font-weight-heading); --font-color-heading-3: var(--font-color-heading); --font-family-heading-3: var(--font-family-heading); --font-line-height-heading-3: 30px; - --font-size-heading-3: 20px; + --font-size-heading-3: var(--utrecht-heading-3-font-size); --font-weight-heading-3: var(--font-weight-heading); --font-color-heading-4: var(--font-color-heading); --font-family-heading-4: var(--font-family-heading); --font-line-height-heading-4: 26px; - --font-size-heading-4: 16px; + --font-size-heading-4: var(--utrecht-heading-4-font-size); --font-weight-heading-4: var(--font-weight-heading); --font-size-heading-card: 18px; --font-family-body: 'Body'; --font-color-body: var(--color-gray-dark); - --font-size-body: 16px; + --font-size-body: var(--utrecht-document-font-size); --font-line-height-body: 24px; --font-size-body-extra-small: 12px; - --font-size-body-small: 14px; + --font-size-body-small: var(--utrecht-paragraph-small-font-size); --font-size-body-large: 20px; --font-line-height-body-small: 20px; --font-line-height-body-medium: 24px; --font-line-height-body-large: 28px; - /// Spacing. - --row-height: 40px; --row-height-small: 24px; --row-height-big: 48px; From 6907bf67deab0ff05b8eddb878c3e92bb237d93f Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Thu, 6 Jun 2024 14:38:24 +0200 Subject: [PATCH 02/24] [#2503] Refactored font-size enlarger with encapsulated modularization --- .../components/accessibility/enlarge_font.js | 151 +++++++++--------- src/open_inwoner/scss/views/App.scss | 6 +- 2 files changed, 76 insertions(+), 81 deletions(-) diff --git a/src/open_inwoner/js/components/accessibility/enlarge_font.js b/src/open_inwoner/js/components/accessibility/enlarge_font.js index 1aeca392fc..1a0ea2d93c 100644 --- a/src/open_inwoner/js/components/accessibility/enlarge_font.js +++ b/src/open_inwoner/js/components/accessibility/enlarge_font.js @@ -1,96 +1,89 @@ export class EnlargeFont { static selector = '.accessibility--enlarge-font' + constructor(node) { this.node = node this.text = node.querySelector('.link__text') this.icon = node.querySelector('.material-icons') this.node.addEventListener('click', this.enlarge.bind(this)) - // Set initial values for default styles - let root = document.documentElement - const bodyFontSizeDefault = '--utrecht-document-font-size' - const paragraphFontSizeDefault = '--utrecht-paragraph-font-size' - const paragraphFontSizeSmall = '--utrecht-paragraph-small-font-size' - const headingThreeFontSize = '--utrecht-heading-3-font-size' - const headingFourFontSize = '--utrecht-heading-4-font-size' - const oipBodyFontSizeDefault = '--font-size-body' - const oipBodyFontSizeSmall = '--font-size-body-small' - const oipHeadingThreeFontSize = '--font-size-heading-3' - const oipHeadingFourFontSize = '--font-size-heading-4' - const oipCardHeadingFontSize = '--font-size-heading-card' - const baseSizeDefault = '16px' - const baseSizeSmall = '14px' - const baseHeadingThreeFontSize = '20px' - const baseHeadingFourFontSize = '16px' - const baseCardHeadingFontSize = '18px' + this.root = document.documentElement + + // Target specific sizes that need to be enlarged + this.styles = { + bodyFontSizeDefault: '--utrecht-document-font-size', + paragraphFontSizeDefault: '--utrecht-paragraph-font-size', + paragraphFontSizeSmall: '--utrecht-paragraph-small-font-size', + headingThreeFontSize: '--utrecht-heading-3-font-size', + headingFourFontSize: '--utrecht-heading-4-font-size', + // Legacy styles' initial values + oipBodyFontSizeDefault: '--font-size-body', + oipBodyFontSizeSmall: '--font-size-body-small', + oipHeadingThreeFontSize: '--font-size-heading-3', + oipHeadingFourFontSize: '--font-size-heading-4', + // OIP specific card-heading + oipCardHeadingFontSize: '--font-size-heading-card', + } + + // Set initial values for toggling default styles + this.baseSizes = { + default: '16px', + small: '14px', + headingThree: '20px', + headingFour: '16px', + cardHeading: '18px', + } + + // Enlarge the different types of body-font as well as paragraphs + // and set all lower types of headings (lower than H2) to get the same larger font-size + this.enlargeSizes = { + default: '20px', + small: '17px', + headings: '22px', + } + + this.setInitialStyles() + } + + setInitialStyles() { + Object.keys(this.styles).forEach((key) => { + const sizeKey = this.getSizeKey(key) + this.root.style.setProperty(this.styles[key], this.baseSizes[sizeKey]) + }) + } - root.style.setProperty(bodyFontSizeDefault, baseSizeDefault) - root.style.setProperty(paragraphFontSizeDefault, baseSizeDefault) - root.style.setProperty(paragraphFontSizeSmall, baseSizeSmall) - root.style.setProperty(headingThreeFontSize, baseHeadingThreeFontSize) - root.style.setProperty(headingFourFontSize, baseHeadingFourFontSize) - root.style.setProperty(oipBodyFontSizeDefault, baseSizeDefault) - root.style.setProperty(oipBodyFontSizeSmall, baseSizeSmall) - root.style.setProperty(oipHeadingThreeFontSize, baseHeadingThreeFontSize) - root.style.setProperty(oipHeadingFourFontSize, baseHeadingFourFontSize) - root.style.setProperty(oipCardHeadingFontSize, baseCardHeadingFontSize) + // Target both NL Design System values and legacy variables by their component type + getSizeKey(styleKey) { + if (styleKey.includes('Small')) return 'small' + if (styleKey.includes('Three')) return 'headingThree' + if (styleKey.includes('Four')) return 'headingFour' + if (styleKey.includes('CardHeading')) return 'cardHeading' + return 'default' } enlarge(event) { event.preventDefault() - let root = document.documentElement - // NL design system styles - const bodyFontSizeDefault = '--utrecht-document-font-size' - const paragraphFontSizeDefault = '--utrecht-paragraph-font-size' - const paragraphFontSizeSmall = '--utrecht-paragraph-small-font-size' - const headingThreeFontSize = '--utrecht-heading-3-font-size' - const headingFourFontSize = '--utrecht-heading-4-font-size' - // Legacy styles - const oipBodyFontSizeDefault = '--font-size-body' - const oipBodyFontSizeSmall = '--font-size-body-small' - const oipHeadingThreeFontSize = '--font-size-heading-3' - const oipHeadingFourFontSize = '--font-size-heading-4' - const oipCardHeadingFontSize = '--font-size-heading-card' - // Set toggle variations - const baseSizeDefault = '16px' - const baseSizeSmall = '14px' - const enlargeSizeDefault = '20px' - const enlargeSizeSmall = '17px' - const baseHeadingThreeFontSize = '20px' - const baseHeadingFourFontSize = '16px' - const baseCardHeadingFontSize = '18px' - // Set all lower headings to H2 size - const enlargeHeadings = '22px' - if ( - root.style.getPropertyValue(bodyFontSizeDefault) === enlargeSizeDefault - ) { - root.style.setProperty(bodyFontSizeDefault, baseSizeDefault) - root.style.setProperty(paragraphFontSizeDefault, baseSizeDefault) - root.style.setProperty(paragraphFontSizeSmall, baseSizeSmall) - root.style.setProperty(headingThreeFontSize, baseHeadingThreeFontSize) - root.style.setProperty(headingFourFontSize, baseHeadingFourFontSize) - root.style.setProperty(oipBodyFontSizeDefault, baseSizeDefault) - root.style.setProperty(oipBodyFontSizeSmall, baseSizeSmall) - root.style.setProperty(oipHeadingThreeFontSize, baseHeadingThreeFontSize) - root.style.setProperty(oipHeadingFourFontSize, baseHeadingFourFontSize) - root.style.setProperty(oipCardHeadingFontSize, baseCardHeadingFontSize) - this.text.innerText = this.node.dataset.text - this.icon.innerText = this.node.dataset.icon - } else { - root.style.setProperty(bodyFontSizeDefault, enlargeSizeDefault) - root.style.setProperty(paragraphFontSizeDefault, enlargeSizeDefault) - root.style.setProperty(paragraphFontSizeSmall, enlargeSizeSmall) - root.style.setProperty(headingThreeFontSize, enlargeHeadings) - root.style.setProperty(headingFourFontSize, enlargeHeadings) - root.style.setProperty(oipBodyFontSizeDefault, enlargeSizeDefault) - root.style.setProperty(oipBodyFontSizeSmall, enlargeSizeSmall) - root.style.setProperty(oipHeadingThreeFontSize, enlargeHeadings) - root.style.setProperty(oipHeadingFourFontSize, enlargeHeadings) - root.style.setProperty(oipCardHeadingFontSize, enlargeHeadings) - this.text.innerText = this.node.dataset.altText - this.icon.innerText = this.node.dataset.altIcon - } + const isEnlarged = + this.root.style.getPropertyValue(this.styles.bodyFontSizeDefault) === + this.enlargeSizes.default + + Object.keys(this.styles).forEach((key) => { + const sizeKey = this.getSizeKey(key) + this.root.style.setProperty( + this.styles[key], + isEnlarged + ? this.baseSizes[sizeKey] + : this.enlargeSizes[sizeKey] || this.enlargeSizes.headings + ) + }) + + this.text.innerText = isEnlarged + ? this.node.dataset.text + : this.node.dataset.altText + this.icon.innerText = isEnlarged + ? this.node.dataset.icon + : this.node.dataset.altIcon } } diff --git a/src/open_inwoner/scss/views/App.scss b/src/open_inwoner/scss/views/App.scss index 16f19f881b..5470c40e54 100644 --- a/src/open_inwoner/scss/views/App.scss +++ b/src/open_inwoner/scss/views/App.scss @@ -195,7 +195,8 @@ /// Font. --font-color-heading: var(--color-black); - --font-family-heading: 'Heading'; + // Font-family = 'Heading' coming from both NLDS and/or the font-upload functionality + --font-family-heading: var(--utrecht-heading-font-family); --font-weight-heading: bold; --font-color-heading-1: var(--font-color-heading); @@ -224,7 +225,8 @@ --font-size-heading-card: 18px; - --font-family-body: 'Body'; + // Font-family = 'Body' coming from both NLDS and/or the font-upload functionality + --font-family-body: var(--utrecht-document-font-family); --font-color-body: var(--color-gray-dark); --font-size-body: var(--utrecht-document-font-size); --font-line-height-body: 24px; From 335f38b29f4b7ee29d931e69d68c52de4a5a9a68 Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Tue, 11 Jun 2024 17:11:18 +0200 Subject: [PATCH 03/24] [#2366] Added document name to download attributes --- .../components/templates/components/File/File.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/open_inwoner/components/templates/components/File/File.html b/src/open_inwoner/components/templates/components/File/File.html index 7a03c59646..1c6a1ead34 100644 --- a/src/open_inwoner/components/templates/components/File/File.html +++ b/src/open_inwoner/components/templates/components/File/File.html @@ -18,11 +18,11 @@ {{ name }} {% if extension and size %} - ({{extension}}, {{size|readable_size}}) + ({{ extension }}, {{ size|readable_size }}) {% elif extension %} - ({{extension}}) + ({{ extension }}) {% elif size %} - ({{size|readable_size}}) + ({{ size|readable_size }}) {% endif %} {{ created|date:'j F Y' }} @@ -49,8 +49,8 @@ {% enddropdown %} {% elif show_download %} - {% trans "Download" as download %} - {% link href=url text=download primary=True download=True hide_text=True extra_classes="file__download" icon="download" icon_outlined=True icon_position="before" %} + {% trans "Download " as download %} + {% link href=url text=download|add:name primary=True download=True hide_text=True extra_classes="file__download" icon="download" icon_outlined=True icon_position="before" %} {% endif %} {% if description %}

{{ description }}

{% endif %} From d7bb8eda87691a6079b43620f33fea59a0493f45 Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Thu, 13 Jun 2024 10:08:59 +0200 Subject: [PATCH 04/24] [#2344] Added text alternatives for hidden icons --- src/open_inwoner/templates/pages/cases/statuses.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/open_inwoner/templates/pages/cases/statuses.html b/src/open_inwoner/templates/pages/cases/statuses.html index 4ada17ac29..416d5e8616 100644 --- a/src/open_inwoner/templates/pages/cases/statuses.html +++ b/src/open_inwoner/templates/pages/cases/statuses.html @@ -13,6 +13,7 @@

{% trans 'Status' %}

{% firstof case.id|slugify as id %} {% firstof 'content-'|add:id|add:'-id' as content_id %} {% icon icon="circle" extra_classes="status-step" %} + {% trans 'Niet voltooid' %}

@@ -67,6 +69,7 @@

{% if case.second_status_preview %}
  • {% icon icon="circle_outlined" outlined=True %} + {% trans 'Toekomstige status' %}
    {{ case.second_status_preview.omschrijving }}
    @@ -77,6 +80,7 @@

    {% if case.end_statustype_data %}
  • {% icon icon="circle_outlined" outlined=True %} + {% trans 'Toekomstige voltooide status' %}
    {{ case.end_statustype_data.label }}
    From 92e7a94603203d1f550b0dd4e266978253a0ad41 Mon Sep 17 00:00:00 2001 From: Jiro Ghianni <118177951+jiromaykin@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:40:49 +0200 Subject: [PATCH 05/24] Processed feedback for document download link-text Applied suggestions from code review Co-authored-by: Paul Schilling <89185369+pi-sigma@users.noreply.github.com> --- .../components/templates/components/File/File.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/open_inwoner/components/templates/components/File/File.html b/src/open_inwoner/components/templates/components/File/File.html index 1c6a1ead34..8e8e4c780c 100644 --- a/src/open_inwoner/components/templates/components/File/File.html +++ b/src/open_inwoner/components/templates/components/File/File.html @@ -49,8 +49,8 @@
  • {% enddropdown %} {% elif show_download %} - {% trans "Download " as download %} - {% link href=url text=download|add:name primary=True download=True hide_text=True extra_classes="file__download" icon="download" icon_outlined=True icon_position="before" %} + {% trans "Download" as download %} + {% link href=url text=download|add:" "|add:name primary=True download=True hide_text=True extra_classes="file__download" icon="download" icon_outlined=True icon_position="before" %} {% endif %} {% if description %}

    {{ description }}

    {% endif %} From 60bfd92067d849946de96d721d9f17f2fef6cdba Mon Sep 17 00:00:00 2001 From: Jiro Ghianni <118177951+jiromaykin@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:34:12 +0200 Subject: [PATCH 06/24] Apply quote suggestions from code review Co-authored-by: Paul Schilling <89185369+pi-sigma@users.noreply.github.com> --- src/open_inwoner/templates/pages/cases/statuses.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/open_inwoner/templates/pages/cases/statuses.html b/src/open_inwoner/templates/pages/cases/statuses.html index 416d5e8616..5da56a6ed8 100644 --- a/src/open_inwoner/templates/pages/cases/statuses.html +++ b/src/open_inwoner/templates/pages/cases/statuses.html @@ -13,7 +13,7 @@

    {% trans 'Status' %}

    {% firstof case.id|slugify as id %} {% firstof 'content-'|add:id|add:'-id' as content_id %} {% icon icon="circle" extra_classes="status-step" %} - {% trans 'Niet voltooid' %} + {% trans "Niet voltooid" %}

    @@ -69,7 +69,7 @@

    {% if case.second_status_preview %}
  • {% icon icon="circle_outlined" outlined=True %} - {% trans 'Toekomstige status' %} + {% trans "Toekomstige status" %}
    {{ case.second_status_preview.omschrijving }}
    @@ -80,7 +80,7 @@

    {% if case.end_statustype_data %}
  • {% icon icon="circle_outlined" outlined=True %} - {% trans 'Toekomstige voltooide status' %} + {% trans "Toekomstige voltooide status" %}
    {{ case.end_statustype_data.label }}
    From c2f9332a4d93b5632d2d4ae07179cf49de1c8ef3 Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Thu, 13 Jun 2024 17:18:17 +0200 Subject: [PATCH 07/24] [#2347] Converted TD's to TH's in product pages --- .../scss/components/Table/Table.scss | 160 ++++++++++++------ src/open_inwoner/utils/ckeditor.py | 20 +++ 2 files changed, 126 insertions(+), 54 deletions(-) diff --git a/src/open_inwoner/scss/components/Table/Table.scss b/src/open_inwoner/scss/components/Table/Table.scss index b810f0aba5..42a3e632c5 100644 --- a/src/open_inwoner/scss/components/Table/Table.scss +++ b/src/open_inwoner/scss/components/Table/Table.scss @@ -12,6 +12,27 @@ scrollbar-color: var(--color-secondary) var(--color-gray-lightest); -webkit-overflow-scrolling: touch; + /// Scrollbar style for tables on mobile + + @media (min-width: 768px) { + display: table; + } + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-track { + background: var(--color-gray-lightest); + } + + &::-webkit-scrollbar-thumb { + background-color: var(--color-secondary); + border-radius: 6px; + border: 2px solid var(--color-secondary); + } + &--space-medium { margin-top: var(--spacing-medium); } @@ -40,43 +61,6 @@ } } - &--content { - margin-top: var(--spacing-giant); - margin-bottom: var(--spacing-giant); - - .table__item { - border-right: 1px solid var(--color-gray-light); - } - - .table__item:first-of-type { - font-weight: bold; - padding-left: 0; - } - - .table__item:not(:first-of-type) { - text-align: right; - padding-right: var(--spacing-giant); - } - - .table__header:first-of-type { - padding-left: 0; - } - - .table__header:not(:first-of-type) { - text-align: right; - padding-right: var(--spacing-giant); - } - } - - &__heading { - .table__item { - color: var(--color-gray-lighter); - font-size: 16px; - letter-spacing: 0; - line-height: var(--font-line-height-body-medium); - } - } - &__item { padding: var(--spacing-medium); border-bottom: 1px solid; @@ -119,6 +103,72 @@ } } + /// Product pages + + &--content { + display: table; + border-collapse: separate; + margin-top: var(--spacing-giant); + margin-bottom: var(--spacing-giant); + + .table__item { + border-right: 1px solid var(--color-gray-light); + word-wrap: break-word; + text-wrap: initial; + } + + .table__item:first-of-type { + border-left: 1px solid var(--color-gray-light); + font-weight: bold; + } + + .table__item:not(:first-of-type) { + text-align: right; + padding-right: var(--spacing-giant); + } + + .table__row:first-child .table__header { + font-family: var(--font-family-heading); + font-weight: bold; + border-top: 1px solid var(--color-gray-dark-900); + border-bottom: 1px solid var(--color-gray-dark-900); + } + + .table__header:first-of-type { + border-left: 1px solid var(--color-gray-dark-900); + border-top-left-radius: var(--border-radius); + } + + .table__header:last-of-type { + border-right: 1px solid var(--color-gray-dark-900); + border-top-right-radius: var(--border-radius); + } + + .table__header:not(:first-of-type) { + text-align: right; + padding-right: var(--spacing-giant); + } + + .table__row:last-of-type .table__item:first-of-type { + border-bottom-left-radius: var(--border-radius); + } + + .table__row:last-of-type .table__item:last-of-type { + border-bottom-right-radius: var(--border-radius); + } + } + + /// Plans & Actions + + &__heading { + .table__item { + color: var(--color-gray-lighter); + font-size: 16px; + letter-spacing: 0; + line-height: var(--font-line-height-body-medium); + } + } + &__header { padding: var(--spacing-medium); text-align: left; @@ -155,24 +205,26 @@ } } - @media (min-width: 768px) { - display: table; - } -} + /// + /// Legacy styling + /// -/// Scrollbar style for tables on mobile - -.table::-webkit-scrollbar { - width: 8px; - height: 8px; -} + &--content { + .table__row:first-child .table__item { + font-family: var(--font-family-heading); + font-weight: bold; + border-top: 1px solid var(--color-gray-dark-900); + border-bottom: 1px solid var(--color-gray-dark-900); + } -.table::-webkit-scrollbar-track { - background: var(--color-gray-lightest); -} + .table__row:first-of-type .table__item:first-of-type { + border-left: 1px solid var(--color-gray-dark-900); + border-top-left-radius: var(--border-radius); + } -.table::-webkit-scrollbar-thumb { - background-color: var(--color-secondary); - border-radius: 6px; - border: 2px solid var(--color-secondary); + .table__row:first-of-type .table__item:last-of-type { + border-right: 1px solid var(--color-gray-dark-900); + border-top-right-radius: var(--border-radius); + } + } } diff --git a/src/open_inwoner/utils/ckeditor.py b/src/open_inwoner/utils/ckeditor.py index f7801fec63..697379c22e 100644 --- a/src/open_inwoner/utils/ckeditor.py +++ b/src/open_inwoner/utils/ckeditor.py @@ -16,11 +16,29 @@ ("p", "utrecht-paragraph"), ("a", "link link--secondary"), ("table", "table table--content"), + ("thead", "table__heading"), + ("tbody", "table__body"), + ("tr", "table__row"), ("th", "table__header"), ("td", "table__item"), ] +def convert_first_row_to_th(html_tables): + """ + Converts the first row of all tables from td to th. + """ + for table in html_tables.find_all("table"): + first_row = table.find("tr") + if first_row: + for cell in first_row.find_all("td"): + th = html_tables.new_tag("th") + th.string = cell.string + th.attrs = cell.attrs + th["class"] = "table__header" + cell.replace_with(th) + + def get_rendered_content(content: str) -> str: """ Takes object's content as an input and returns the rendered one. @@ -50,6 +68,8 @@ def get_product_rendered_content(product): html = md.convert(content) soup = BeautifulSoup(html, "html.parser") + convert_first_row_to_th(soup) + for tag, class_name in CLASS_ADDERS: for element in soup.find_all(tag): if element.attrs.get("class") and "cta-button" in element.attrs["class"]: From 67f2d2bacaac5cc7de6b3a7fb9e14a53e19577aa Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Mon, 17 Jun 2024 12:49:07 +0200 Subject: [PATCH 08/24] [#2560] Treat catalogus as a required field on zaaktype response Previously we allowed for two edge-cases in resolving zaaktypes: 1. We allowed for catalogus to be absent on a ZaakType to accommodate eSuite, in which the field was absent. 2. We allowed for importing a ZaakType if it included a URL pointing to a Catalogus that had not previously been created. This commit no longer allows for these exceptions. With regards to 1, eSuite has since implemented changes to include the catalogus field. As for 2: it was already an edge-case we felt should raise rather than be suppressed, but this is especially true now as we're moving to support multiple ZGW backends. In that scenario, we'll clearly want to only include ZaakTypes from a known Catalogus API, to avoid cross-backend ambiguity. --- src/open_inwoner/openzaak/api_models.py | 3 +- .../openzaak/tests/test_zgw_imports.py | 45 ++---------- .../tests/test_zgw_imports_command.py | 72 +------------------ src/open_inwoner/openzaak/zgw_imports.py | 20 +++--- 4 files changed, 17 insertions(+), 123 deletions(-) diff --git a/src/open_inwoner/openzaak/api_models.py b/src/open_inwoner/openzaak/api_models.py index 83bde4f344..81e0a41a96 100644 --- a/src/open_inwoner/openzaak/api_models.py +++ b/src/open_inwoner/openzaak/api_models.py @@ -109,6 +109,7 @@ class ZaakType(ZGWModel): url: str identificatie: str omschrijving: str + catalogus: str vertrouwelijkheidaanduiding: str doel: str aanleiding: str @@ -129,8 +130,6 @@ class ZaakType(ZGWModel): # roltypen: list # besluittypen: list - # catalogus not on eSuite - catalogus: str = "" begin_geldigheid: Optional[date] = None einde_geldigheid: Optional[date] = None versiedatum: Optional[date] = None diff --git a/src/open_inwoner/openzaak/tests/test_zgw_imports.py b/src/open_inwoner/openzaak/tests/test_zgw_imports.py index e3e5edd2f9..257f24ba20 100644 --- a/src/open_inwoner/openzaak/tests/test_zgw_imports.py +++ b/src/open_inwoner/openzaak/tests/test_zgw_imports.py @@ -118,11 +118,7 @@ def __init__(self): self.extra_zaaktype, ] - def install_mocks(self, m, *, with_catalog=True) -> "ZaakTypeMockData": - if not with_catalog: - for zt in self.all_zaak_types: - zt["catalogus"] = None - + def install_mocks(self, m) -> "ZaakTypeMockData": m.get( f"{CATALOGI_ROOT}zaaktypen", json=paginated_response(self.zaak_types), @@ -238,38 +234,9 @@ def test_import_zaaktype_configs_with_catalogs(self, m): def test_import_zaaktype_configs_without_catalogs(self, m): data = ZaakTypeMockData() - data.install_mocks(m, with_catalog=False) - - res = import_zaaktype_configs() - - # first two got added, third one has same identificatie, fourth one is internal - self.assertEqual(len(res), 2) - self.assertEqual(ZaakTypeConfig.objects.count(), 2) - - for i, config in enumerate(res): - self.assertEqual(config.identificatie, data.zaak_types[i]["identificatie"]) - self.assertEqual(config.omschrijving, data.zaak_types[i]["omschrijving"]) - self.assertIsNone(config.catalogus) - - # run again with same API response - res = import_zaaktype_configs() - - # nothing got added - self.assertEqual(len(res), 0) - self.assertEqual(ZaakTypeConfig.objects.count(), 2) - - # add more elements to API response and run again - m.get( - f"{CATALOGI_ROOT}zaaktypen", - json=paginated_response([data.extra_zaaktype] + data.zaak_types), - ) - res = import_zaaktype_configs() - - # one got added - self.assertEqual(len(res), 1) - self.assertEqual(ZaakTypeConfig.objects.count(), 3) + data.install_mocks(m) - config = res[0] - self.assertEqual(config.identificatie, data.extra_zaaktype["identificatie"]) - self.assertEqual(config.omschrijving, data.extra_zaaktype["omschrijving"]) - self.assertIsNone(config.catalogus) + with self.assertRaises( + RuntimeError, msg="Catalogus must exist prior to import" + ): + import_zaaktype_configs() diff --git a/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py b/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py index 21b71a6e31..e10a9ed8de 100644 --- a/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py +++ b/src/open_inwoner/openzaak/tests/test_zgw_imports_command.py @@ -122,73 +122,5 @@ def test_zgw_import_data_command_without_catalog(self, m): ) InformationObjectTypeMockData().install_mocks(m, with_catalog=False) - # # run it to import our data - out = StringIO() - call_command("zgw_import_data", stdout=out) - - self.assertEqual(CatalogusConfig.objects.count(), 0) - self.assertEqual(ZaakTypeConfig.objects.count(), 2) - self.assertEqual(ZaakTypeInformatieObjectTypeConfig.objects.count(), 3) - self.assertEqual(ZaakTypeStatusTypeConfig.objects.count(), 2) - self.assertEqual(ZaakTypeResultaatTypeConfig.objects.count(), 2) - - stdout = out.getvalue().strip() - - expected = inspect.cleandoc( - """ - imported 0 new catalogus configs - - imported 2 new zaaktype configs - AAA - zaaktype-aaa - BBB - zaaktype-bbb - - imported 3 new zaaktype-informatiebjecttype configs - AAA - zaaktype-aaa - info-aaa-1 - info-aaa-2 - BBB - zaaktype-bbb - info-bbb - - imported 2 new zaaktype-statustype configs - AAA - zaaktype-aaa - AAA - status-aaa-1 - AAA - status-aaa-2 - - imported 2 new zaaktype-resultaattype configs - AAA - zaaktype-aaa - AAA - test - BBB - zaaktype-bbb - BBB - test - """ - ).strip() - - self.assertEqual(stdout, expected) - - # run it again without changes - out = StringIO() - call_command("zgw_import_data", stdout=out) - - # still same - self.assertEqual(CatalogusConfig.objects.count(), 0) - self.assertEqual(ZaakTypeConfig.objects.count(), 2) - self.assertEqual(ZaakTypeInformatieObjectTypeConfig.objects.count(), 3) - self.assertEqual(ZaakTypeStatusTypeConfig.objects.count(), 2) - self.assertEqual(ZaakTypeResultaatTypeConfig.objects.count(), 2) - - stdout = out.getvalue().strip() - - expected = inspect.cleandoc( - """ - imported 0 new catalogus configs - - imported 0 new zaaktype configs - - imported 0 new zaaktype-informatiebjecttype configs - - imported 0 new zaaktype-statustype configs - - imported 0 new zaaktype-resultaattype configs - """ - ).strip() - - self.assertEqual(stdout, expected) + with self.assertRaises(RuntimeError): + call_command("zgw_import_data") diff --git a/src/open_inwoner/openzaak/zgw_imports.py b/src/open_inwoner/openzaak/zgw_imports.py index a8dd467b6d..92b24d0719 100644 --- a/src/open_inwoner/openzaak/zgw_imports.py +++ b/src/open_inwoner/openzaak/zgw_imports.py @@ -107,20 +107,16 @@ def import_zaaktype_configs() -> list[ZaakTypeConfig]: ) for zaak_type in zaak_types: - catalog = None - if zaak_type.catalogus: - catalog = catalog_lookup.get(zaak_type.catalogus) - if not catalog: - # weird edge-case: if the zaak_type has a catalogus-url but we don't have the object - # TODO this is bad, log/raise something - pass + try: + catalog = catalog_lookup[zaak_type.catalogus] + except KeyError as exc: + raise RuntimeError( + f"ZaakType `{zaak_type.url}` points to a Catalogus at" + f" `{zaak_type.catalogus}` which is not currently configured." + ) from exc # make key for de-duplication and collapsing related zaak-types on their 'identificatie' - if catalog: - key = (catalog.id, zaak_type.identificatie) - else: - key = (None, zaak_type.identificatie) - + key = (catalog.id, zaak_type.identificatie) if key not in known_keys: known_keys.add(key) create[key] = ZaakTypeConfig( From 0133e169099780cfc503e6d6a2fd8a0a66db2771 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Tue, 18 Jun 2024 09:19:46 +0200 Subject: [PATCH 09/24] :arrow_up: [#2562] Bump lxml and xmlsec to their latest versions xmlsec has done efforts to support binary wheels together with lxml to avoid compile/installation/runtime issues, which should also speed up installation since no source compilation is required. Commit message from eeac088fe42ce943dbd904437066c3f9e7c9f830: Several teammembers have faced sporadic issues installing xmlsec due to missing binaries and the resulting difficulties in building the packages with the right library versions. Requiring at least 1.3.14 allows us to use the pre-built binaries. --- requirements/base.txt | 4 ++-- requirements/ci.txt | 4 ++-- requirements/dev.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 1e9efa54f8..067fb26add 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -379,7 +379,7 @@ jsonschema==4.1.0 # via drf-spectacular kombu==5.3.7 # via celery -lxml==4.9.1 +lxml==5.2.2 # via # django-digid-eherkenning # mail-editor @@ -581,7 +581,7 @@ xlrd==2.0.1 # via tablib xlwt==1.3.0 # via tablib -xmlsec==1.3.12 +xmlsec==1.3.14 # via maykin-python3-saml xsdata==23.8 # via -r requirements/base.in diff --git a/requirements/ci.txt b/requirements/ci.txt index 23dc3d7761..b562831e2f 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -691,7 +691,7 @@ kombu==5.3.7 # celery lazy-object-proxy==1.6.0 # via astroid -lxml==4.9.1 +lxml==5.2.2 # via # -c requirements/base.txt # -r requirements/base.txt @@ -1134,7 +1134,7 @@ xlwt==1.3.0 # -c requirements/base.txt # -r requirements/base.txt # tablib -xmlsec==1.3.12 +xmlsec==1.3.14 # via # -c requirements/base.txt # -r requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index e2427937b9..a2c814046c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -789,7 +789,7 @@ lazy-object-proxy==1.6.0 # astroid locust==2.20.0 # via -r requirements/dev.in -lxml==4.9.1 +lxml==5.2.2 # via # -c requirements/ci.txt # -r requirements/ci.txt @@ -1352,7 +1352,7 @@ xlwt==1.3.0 # -c requirements/ci.txt # -r requirements/ci.txt # tablib -xmlsec==1.3.12 +xmlsec==1.3.14 # via # -c requirements/ci.txt # -r requirements/ci.txt From f2ce234acf08d1cde6c81c93e5cab3fff7a3d3ac Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Mon, 17 Jun 2024 17:38:35 +0200 Subject: [PATCH 10/24] [#2340] Made footer logo use same Filer alt as header logo --- src/open_inwoner/templates/master.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/open_inwoner/templates/master.html b/src/open_inwoner/templates/master.html index d8fb834262..106a92c329 100644 --- a/src/open_inwoner/templates/master.html +++ b/src/open_inwoner/templates/master.html @@ -149,7 +149,8 @@ {% endif %} {% if footer.logo %} {% endif %}
  • From 52957af5c18f6f4bdd36492a24115dd5c21aecd0 Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Fri, 14 Jun 2024 15:19:30 +0200 Subject: [PATCH 11/24] Explicate typing for zgw client factory --- src/open_inwoner/openzaak/clients.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/open_inwoner/openzaak/clients.py b/src/open_inwoner/openzaak/clients.py index 7ee1972121..d0e1ea452e 100644 --- a/src/open_inwoner/openzaak/clients.py +++ b/src/open_inwoner/openzaak/clients.py @@ -1,6 +1,7 @@ import base64 import logging from datetime import date +from typing import Literal, Mapping, Type, TypeAlias from django.conf import settings from django.core.files.uploadedfile import InMemoryUploadedFile @@ -680,9 +681,15 @@ def fetch_open_tasks(self, bsn: str) -> list[OpenTask]: return results -def _build_zgw_client(type_) -> APIClient | None: +ZgwClientType = Literal["zaak", "catalogi", "document", "form"] +ZgwClientFactoryReturn: TypeAlias = ( + ZakenClient | CatalogiClient | DocumentenClient | FormClient +) + + +def _build_zgw_client(type_: ZgwClientType) -> ZgwClientFactoryReturn | None: config = OpenZaakConfig.get_solo() - services_to_client_mapping = { + services_to_client_mapping: Mapping[ZgwClientType, Type[ZgwClientFactoryReturn]] = { "zaak": ZakenClient, "catalogi": CatalogiClient, "document": DocumentenClient, From b0d11e1a815980a99a1bf34d7c5d2aad818c7e80 Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Tue, 18 Jun 2024 16:31:12 +0200 Subject: [PATCH 12/24] Create a factory for ZGWApiGroupConfig --- src/open_inwoner/openzaak/tests/factories.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/open_inwoner/openzaak/tests/factories.py b/src/open_inwoner/openzaak/tests/factories.py index 63eef20b32..ef8cbc5d31 100644 --- a/src/open_inwoner/openzaak/tests/factories.py +++ b/src/open_inwoner/openzaak/tests/factories.py @@ -4,6 +4,7 @@ from simple_certmanager.models import Certificate from zgw_consumers.api_models.base import factory as zwg_factory from zgw_consumers.api_models.constants import RolOmschrijving +from zgw_consumers.constants import APITypes from zgw_consumers.models import Service from zgw_consumers.test import generate_oas_component @@ -11,11 +12,13 @@ from open_inwoner.openzaak.api_models import Notification, Rol from open_inwoner.openzaak.models import ( CatalogusConfig, + OpenZaakConfig, UserCaseInfoObjectNotification, UserCaseStatusNotification, ZaakTypeConfig, ZaakTypeInformatieObjectTypeConfig, ZaakTypeStatusTypeConfig, + ZGWApiGroupConfig, ) @@ -27,6 +30,18 @@ class Meta: model = Service +class ZGWApiGroupConfigFactory(factory.django.DjangoModelFactory): + name = factory.Sequence(lambda n: f"API-{n}") + open_zaak_config = factory.LazyAttribute(lambda _: OpenZaakConfig.get_solo()) + zrc_service = factory.SubFactory(ServiceFactory, api_type=APITypes.zrc) + drc_service = factory.SubFactory(ServiceFactory, api_type=APITypes.drc) + ztc_service = factory.SubFactory(ServiceFactory, api_type=APITypes.ztc) + form_service = factory.SubFactory(ServiceFactory, api_type=APITypes.orc) + + class Meta: + model = ZGWApiGroupConfig + + class CertificateFactory(factory.django.DjangoModelFactory): label = factory.Sequence(lambda n: f"Certificate-{n}") From 2b120c2744840d7d23932ff4e80b44c294333a51 Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Tue, 18 Jun 2024 16:39:12 +0200 Subject: [PATCH 13/24] [#2566] Persist originating service on zgw clients To support multiple zgw backends, we frequently need to determine which backend to query for which resource. As a first step to keeping better track of where ZGW objects come from, in this commit we persist the zgw service used to configure a client on the client object, so that we can later fetch and persist this service to facilitate easier target backend resolution. --- src/open_inwoner/openzaak/clients.py | 23 +++++++++++---- .../openzaak/tests/test_clients.py | 28 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/open_inwoner/openzaak/tests/test_clients.py diff --git a/src/open_inwoner/openzaak/clients.py b/src/open_inwoner/openzaak/clients.py index d0e1ea452e..6d8f4f61b0 100644 --- a/src/open_inwoner/openzaak/clients.py +++ b/src/open_inwoner/openzaak/clients.py @@ -13,6 +13,7 @@ from zgw_consumers.api_models.catalogi import Catalogus from zgw_consumers.api_models.constants import RolOmschrijving, RolTypes from zgw_consumers.client import build_client +from zgw_consumers.models import Service from zgw_consumers.service import pagination_helper from open_inwoner.openzaak.api_models import InformatieObject @@ -39,7 +40,17 @@ logger = logging.getLogger(__name__) -class ZakenClient(APIClient): +class ZgwAPIClient(APIClient): + """A client for interacting with ZGW services.""" + + configured_from: Service + + def __init__(self, *args, **kwargs): + self.configured_from = kwargs.pop("configured_from") + super().__init__(*args, **kwargs) + + +class ZakenClient(ZgwAPIClient): def fetch_cases( self, user_bsn: str | None = None, @@ -424,7 +435,7 @@ def connect_case_with_document( return data -class CatalogiClient(APIClient): +class CatalogiClient(ZgwAPIClient): # not cached because only used by tools, # and because caching (stale) listings can break lookups def fetch_status_types_no_cache(self, case_type_url: str) -> list[StatusType]: @@ -583,7 +594,7 @@ def fetch_single_information_object_type( return information_object_type -class DocumentenClient(APIClient): +class DocumentenClient(ZgwAPIClient): def _fetch_single_information_object( self, *, url: str | None = None, uuid: str | None = None ) -> InformatieObject | None: @@ -645,7 +656,7 @@ def upload_document( return data -class FormClient(APIClient): +class FormClient(ZgwAPIClient): def fetch_open_submissions(self, bsn: str) -> list[OpenSubmission]: if not bsn: return [] @@ -698,7 +709,9 @@ def _build_zgw_client(type_: ZgwClientType) -> ZgwClientFactoryReturn | None: if client_class := services_to_client_mapping.get(type_): service = getattr(config, f"{type_}_service") if service: - client = build_client(service, client_factory=client_class) + client = build_client( + service, client_factory=client_class, configured_from=service + ) return client logger.warning("no service defined for %s", type_) diff --git a/src/open_inwoner/openzaak/tests/test_clients.py b/src/open_inwoner/openzaak/tests/test_clients.py new file mode 100644 index 0000000000..5875f9f36c --- /dev/null +++ b/src/open_inwoner/openzaak/tests/test_clients.py @@ -0,0 +1,28 @@ +from django.test import TestCase + +from zgw_consumers.constants import APITypes +from zgw_consumers.models import Service + +from open_inwoner.openzaak.clients import ( + build_catalogi_client, + build_documenten_client, + build_forms_client, + build_zaken_client, +) +from open_inwoner.openzaak.tests.factories import ZGWApiGroupConfigFactory + + +class ClientFactoryTestCase(TestCase): + def setUp(self): + ZGWApiGroupConfigFactory() + + def test_originating_service_is_persisted_on_client(self): + for factory, api_type in ( + (build_forms_client, APITypes.orc), + (build_zaken_client, APITypes.zrc), + (build_documenten_client, APITypes.drc), + (build_catalogi_client, APITypes.ztc), + ): + client = factory() + self.assertIsInstance(client.configured_from, Service) + self.assertEqual(client.configured_from.api_type, api_type) From 740533fb578bbe7530aea04992eb71f5478cb72b Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Wed, 19 Jun 2024 12:44:45 +0200 Subject: [PATCH 14/24] Bump uwsgi to latest patch release --- requirements/base.txt | 2 +- requirements/ci.txt | 2 +- requirements/dev.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 067fb26add..2797ca9138 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -557,7 +557,7 @@ urllib3==1.26.18 # elasticsearch # requests # sentry-sdk -uwsgi==2.0.23 +uwsgi==2.0.26 # via -r requirements/base.in vine==5.1.0 # via diff --git a/requirements/ci.txt b/requirements/ci.txt index b562831e2f..66b79845d0 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1080,7 +1080,7 @@ urllib3==1.26.18 # elasticsearch # requests # sentry-sdk -uwsgi==2.0.23 +uwsgi==2.0.26 # via # -c requirements/base.txt # -r requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index a2c814046c..0024dd0807 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1283,7 +1283,7 @@ urllib3==1.26.18 # elasticsearch # requests # sentry-sdk -uwsgi==2.0.23 +uwsgi==2.0.26 # via # -c requirements/ci.txt # -r requirements/ci.txt From 2ad93dcaaec37b5a80f1ac4fc78198be87d11c36 Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Wed, 19 Jun 2024 12:46:44 +0200 Subject: [PATCH 15/24] Remove lxml dev packages from CI and Dockerfile Since bumping our lxml/xmlsec requirements, we can utilize the pre-built binaries and no longer have a need for the *-dev system packages. Apart from making things leaner, this also addresses the following issue in python-xmlsec: https://github.com/xmlsec/python-xmlsec/issues/320 --- .github/workflows/ci.yml | 6 +++--- .github/workflows/code-quality.yml | 10 +++++----- Dockerfile | 3 --- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 631ea5826d..1ac120bb7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: Set up backend environment uses: maykinmedia/setup-django-backend@v1 with: - apt-packages: 'libxml2-dev libxmlsec1-dev libxmlsec1-openssl gettext postgresql-client libgdal-dev gdal-bin' + apt-packages: 'libxml2 libxmlsec1 libxmlsec1-openssl gettext postgresql-client libgdal-dev gdal-bin' python-version: '3.11' optimize-postgres: 'yes' pg-service: 'postgres' @@ -116,7 +116,7 @@ jobs: - name: Set up backend environment uses: maykinmedia/setup-django-backend@v1 with: - apt-packages: 'libxml2-dev libxmlsec1-dev libxmlsec1-openssl gettext postgresql-client libgdal-dev gdal-bin' + apt-packages: 'libxml2 libxmlsec1 libxmlsec1-openssl gettext postgresql-client libgdal-dev gdal-bin' python-version: '3.11' optimize-postgres: 'yes' pg-service: 'postgres' @@ -157,7 +157,7 @@ jobs: - name: Set up backend environment uses: maykinmedia/setup-django-backend@v1 with: - apt-packages: 'libxml2-dev libxmlsec1-dev libxmlsec1-openssl gettext libgdal-dev gdal-bin graphviz' + apt-packages: 'libxml2 libxmlsec1 libxmlsec1-openssl gettext libgdal-dev gdal-bin graphviz' python-version: '3.11' setup-node: 'no' diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index ad2a55584a..92f46b45c7 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -19,7 +19,7 @@ jobs: - name: Install libxml run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev libxmlsec1-openssl + sudo apt-get install -y libxml2 libxmlsec1 libxmlsec1-openssl - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: @@ -38,7 +38,7 @@ jobs: - name: Install libxml run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev libxmlsec1-openssl + sudo apt-get install -y libxml2 libxmlsec1 libxmlsec1-openssl - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: @@ -59,7 +59,7 @@ jobs: - name: Set up backend environment uses: maykinmedia/setup-django-backend@v1 with: - apt-packages: 'libxml2-dev libxmlsec1-dev libxmlsec1-openssl gdal-bin' + apt-packages: 'libxml2 libxmlsec1 libxmlsec1-openssl gdal-bin' python-version: '3.11' setup-node: 'no' - name: Run flake8 @@ -111,7 +111,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev libxmlsec1-openssl + sudo apt-get install -y libxml2 libxmlsec1 libxmlsec1-openssl pip install -r requirements/dev.txt - name: Run manage.py makemigrations --check --dry-run run: | @@ -143,7 +143,7 @@ jobs: - name: Install libxml run: | sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev libxmlsec1-openssl + sudo apt-get install -y libxml2 libxmlsec1 libxmlsec1-openssl - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: diff --git a/Dockerfile b/Dockerfile index 79df344d78..7e6bf4da55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ git \ libpq-dev \ - libxml2-dev \ - libxmlsec1-dev \ libxmlsec1-openssl \ libgdk-pixbuf2.0-0 \ libffi-dev \ @@ -71,7 +69,6 @@ RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-reco libgdal32 \ libgeos-c1v5 \ libproj25 \ - libxmlsec1-dev \ libxmlsec1-openssl \ libgdk-pixbuf2.0-0 \ libffi-dev \ From 7a40ed4b8333c8dd4c9f7f1bce59a05b2b248a6f Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Wed, 19 Jun 2024 16:17:43 +0200 Subject: [PATCH 16/24] Refactor TestMigrations utility to pave the way for failing migration tests --- src/open_inwoner/kvk/tests/test_migrations.py | 4 +- .../openzaak/tests/test_migrations.py | 4 +- .../utils/tests/test_migrations.py | 66 +++++++++++++------ 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/open_inwoner/kvk/tests/test_migrations.py b/src/open_inwoner/kvk/tests/test_migrations.py index f1c3aa45fc..c0f6b6e6b4 100644 --- a/src/open_inwoner/kvk/tests/test_migrations.py +++ b/src/open_inwoner/kvk/tests/test_migrations.py @@ -1,7 +1,7 @@ -from open_inwoner.utils.tests.test_migrations import TestMigrations +from open_inwoner.utils.tests.test_migrations import TestSuccessfulMigrations -class APIRootMigrationTest(TestMigrations): +class APIRootMigrationTest(TestSuccessfulMigrations): migrate_from = "0002_alter_kvkconfig_api_root" migrate_to = "0003_api_root" app = "kvk" diff --git a/src/open_inwoner/openzaak/tests/test_migrations.py b/src/open_inwoner/openzaak/tests/test_migrations.py index 27bb327bb9..128ee04526 100644 --- a/src/open_inwoner/openzaak/tests/test_migrations.py +++ b/src/open_inwoner/openzaak/tests/test_migrations.py @@ -1,10 +1,10 @@ from zgw_consumers.constants import APITypes from open_inwoner.openzaak.tests.factories import ServiceFactory -from open_inwoner.utils.tests.test_migrations import TestMigrations +from open_inwoner.utils.tests.test_migrations import TestSuccessfulMigrations -class TestMultiZGWBackendMigrations(TestMigrations): +class TestMultiZGWBackendMigrations(TestSuccessfulMigrations): migrate_from = "0047_delete_statustranslation" migrate_to = "0051_drop_root_zgw_fields" app = "openzaak" diff --git a/src/open_inwoner/utils/tests/test_migrations.py b/src/open_inwoner/utils/tests/test_migrations.py index b4b61c8788..43eeb64ad5 100644 --- a/src/open_inwoner/utils/tests/test_migrations.py +++ b/src/open_inwoner/utils/tests/test_migrations.py @@ -1,21 +1,34 @@ +import contextlib + from django.db import connection from django.db.migrations.executor import MigrationExecutor from django.test import TestCase -class TestMigrations(TestCase): +class TestMigrationsBase(TestCase): + app = None + migrate_from = None + migrate_to = None + extra_migrate_from: list[tuple[str, str]] = None """ Test the effect of applying a migration Adapted from https://github.com/open-formulieren/open-forms/blob/e64c9368264d3f662542866e2d7d5ba15e0f265c/src/openforms/utils/tests/test_migrations.py """ - app = None - migrate_from = None - migrate_to = None + @contextlib.contextmanager + def _immediate_constraints(self): + try: + # Force immediate constraint checks to stop error 'cannot ALTER TABLE "<..>" + # because it has pending trigger events' in the tests + with connection.cursor() as cursor: + cursor.execute("SET CONSTRAINTS ALL IMMEDIATE") - extra_migrate_from: list[tuple[str, str]] = None + yield + finally: + with connection.cursor() as cursor: + cursor.execute("SET CONSTRAINTS ALL DEFERRED") - def setUp(self): + def _revert_to_migrate_from(self): assert self.migrate_from and self.migrate_to and self.app, ( "TestCase '%s' must define migrate_from, migrate_to and app properties" % type(self).__name__ @@ -28,27 +41,38 @@ def setUp(self): self.migrate_to = [(self.app, self.migrate_to)] executor = MigrationExecutor(connection) - old_apps = executor.loader.project_state(self.migrate_from).apps - - # Force immediate constraint checks to stop error 'cannot ALTER TABLE "<..>" because it has pending trigger events' in the tests - with connection.cursor() as cursor: - cursor.execute("SET CONSTRAINTS ALL IMMEDIATE") + self.old_apps = executor.loader.project_state(self.migrate_from).apps - # Reverse to the original migration - executor.migrate(self.migrate_from) - - self.setUpBeforeMigration(old_apps) + with self._immediate_constraints(): + # Reverse to the original migration + executor.migrate(self.migrate_from) + def _apply_migration_to(self): # Run the migration to test executor = MigrationExecutor(connection) executor.loader.build_graph() # reload. - executor.migrate(self.migrate_to) - - # Restore constraint checks - with connection.cursor() as cursor: - cursor.execute("SET CONSTRAINTS ALL DEFERRED") - + with self._immediate_constraints(): + executor.migrate(self.migrate_to) self.apps = executor.loader.project_state(self.migrate_to).apps def setUpBeforeMigration(self, apps): pass + + +class TestSuccessfulMigrations(TestMigrationsBase): + """Test a successful migration. + + Set the class attributes `app`, `migration_from`, and `migrate_to`. You + can specify your pre-migration state in `setUpBeforeMigration()`, and + consequently assert against the resulting state (in your test, do + not import models directly but use: + + ``` + MyModel = self.apps.get_model(self.app, "MyModel") + ``` + """ + + def setUp(self): + self._revert_to_migrate_from() + self.setUpBeforeMigration(self.old_apps) + self._apply_migration_to() From cf03b3da58d6e4438270fa3a78b986fbc4bfc1c8 Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Wed, 19 Jun 2024 17:40:39 +0200 Subject: [PATCH 17/24] Expand the migration testing utility to support failing cases --- .../utils/tests/test_migrations.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/open_inwoner/utils/tests/test_migrations.py b/src/open_inwoner/utils/tests/test_migrations.py index 43eeb64ad5..3cf38bbcb7 100644 --- a/src/open_inwoner/utils/tests/test_migrations.py +++ b/src/open_inwoner/utils/tests/test_migrations.py @@ -76,3 +76,32 @@ def setUp(self): self._revert_to_migrate_from() self.setUpBeforeMigration(self.old_apps) self._apply_migration_to() + + +class TestFailingMigrations(TestMigrationsBase): + """Test a migration which is expected to fail. + + Set the class attributes `app`, `migration_from`, and `migrate_to`. You + can specify your pre-migration state in `setUpBeforeMigration()`, though + you should not import models directly, but rather use: + + ``` + MyModel = self.apps.get_model(self.app, "MyModel") + ``` + + In your test, you can attempt to migration using `self.attempt_migration()` + and assert against the expected exception: + ``` + def test_migration_should_fail(self): + # Setup failure conditions + self.assertRaises(SomeError): + self.attempt_migration() + ``` + """ + + def setUp(self): + self._revert_to_migrate_from() + self.setUpBeforeMigration(self.old_apps) + + def attempt_migration(self): + self._apply_migration_to() From a4bd73ecb8896b4a06d372bbe854ae93ec530691 Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Thu, 20 Jun 2024 12:59:53 +0200 Subject: [PATCH 18/24] [#2357] Set correct overflow for 400% zoom --- src/open_inwoner/scss/components/ReadMore/ReadMore.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/open_inwoner/scss/components/ReadMore/ReadMore.scss b/src/open_inwoner/scss/components/ReadMore/ReadMore.scss index bb8ae55bd2..531316db8c 100644 --- a/src/open_inwoner/scss/components/ReadMore/ReadMore.scss +++ b/src/open_inwoner/scss/components/ReadMore/ReadMore.scss @@ -16,12 +16,12 @@ display: grid; grid-template-rows: 0fr; transition: 0.5s; - overflow: hidden; &--hidden { // hide content if toggle-button is active // always show for screenreaders min-height: 0; + overflow: hidden; } } @@ -41,6 +41,10 @@ .readmore__content { scroll-margin-top: var(--spacing-giant); grid-template-rows: 1fr; + + &--hidden { + overflow: visible; + } } } } From cad0e9628acd559de7bb0fbaafe213e6e38c616c Mon Sep 17 00:00:00 2001 From: Sidney Richards Date: Wed, 19 Jun 2024 16:21:13 +0200 Subject: [PATCH 19/24] [#2569] Persist the source Service on CatalogusConfig --- src/open_inwoner/openzaak/admin.py | 3 + .../0052_add_catalogusconfig_service.py | 69 ++++++++++++++ src/open_inwoner/openzaak/models.py | 8 ++ src/open_inwoner/openzaak/tests/factories.py | 1 + .../openzaak/tests/test_migrations.py | 89 ++++++++++++++++++- src/open_inwoner/openzaak/zgw_imports.py | 1 + 6 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 src/open_inwoner/openzaak/migrations/0052_add_catalogusconfig_service.py diff --git a/src/open_inwoner/openzaak/admin.py b/src/open_inwoner/openzaak/admin.py index 4d1acee815..2145ae5a8c 100644 --- a/src/open_inwoner/openzaak/admin.py +++ b/src/open_inwoner/openzaak/admin.py @@ -72,11 +72,13 @@ class CatalogusConfigAdmin(admin.ModelAdmin): "domein", "rsin", "url", + "service", ] fields = [ "url", "domein", "rsin", + "service", ] readonly_fields = fields search_fields = [ @@ -85,6 +87,7 @@ class CatalogusConfigAdmin(admin.ModelAdmin): "url", ] ordering = ("domein", "rsin") + list_filter = ("service",) class HasDocNotifyListFilter(admin.SimpleListFilter): diff --git a/src/open_inwoner/openzaak/migrations/0052_add_catalogusconfig_service.py b/src/open_inwoner/openzaak/migrations/0052_add_catalogusconfig_service.py new file mode 100644 index 0000000000..0d2934c1c2 --- /dev/null +++ b/src/open_inwoner/openzaak/migrations/0052_add_catalogusconfig_service.py @@ -0,0 +1,69 @@ +# Generated by Django 4.2.11 on 2024-06-19 12:35 + +import django.db.models.deletion +from django.core.exceptions import MultipleObjectsReturned +from django.db import DataError, migrations, models + + +def migrate_catalogus_config_service_field_from_default(apps, schema_editor): + CatalogusConfig = apps.get_model("openzaak", "CatalogusConfig") + ZGWApiGroupConfig = apps.get_model("openzaak", "ZGWApiGroupConfig") + + if not CatalogusConfig.objects.all().exists(): + return + + try: + config = ZGWApiGroupConfig.objects.all().get() + except MultipleObjectsReturned: + raise DataError( + "Attempted to set CatalogusConfig.service using ZGWApiGroupConfig, but there" + " are multiple instances configured. Please (temporarily) ensure you have only" + " a single ZGWApiGroupConfig configured, then run this migration again." + ) + + for catalogus_config in CatalogusConfig.objects.all(): + catalogus_config.service = config.ztc_service + catalogus_config.save() + + +def reverse_migrate_catalogus_config_service_field_from_default(apps, schema_editor): + CatalogusConfig = apps.get_model("openzaak", "CatalogusConfig") + CatalogusConfig.objects.all().update(service=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ("zgw_consumers", "0019_alter_service_uuid"), + ("openzaak", "0051_drop_root_zgw_fields"), + ] + + operations = [ + migrations.AddField( + model_name="catalogusconfig", + name="service", + field=models.ForeignKey( + limit_choices_to={"api_type": "ztc"}, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="catalogus_configs", + to="zgw_consumers.service", + verbose_name="Form API", + ), + ), + migrations.RunPython( + migrate_catalogus_config_service_field_from_default, + reverse_code=reverse_migrate_catalogus_config_service_field_from_default, + ), + migrations.AlterField( + model_name="catalogusconfig", + name="service", + field=models.ForeignKey( + limit_choices_to={"api_type": "ztc"}, + on_delete=django.db.models.deletion.PROTECT, + related_name="catalogus_configs", + to="zgw_consumers.service", + verbose_name="Catalogus API", + ), + ), + ] diff --git a/src/open_inwoner/openzaak/models.py b/src/open_inwoner/openzaak/models.py index 0948a594cd..82f89c4bef 100644 --- a/src/open_inwoner/openzaak/models.py +++ b/src/open_inwoner/openzaak/models.py @@ -293,6 +293,14 @@ class CatalogusConfig(models.Model): verbose_name=_("RSIN"), max_length=9, ) + service = models.ForeignKey( + "zgw_consumers.Service", + verbose_name=_("Catalogus API"), + on_delete=models.PROTECT, + limit_choices_to={"api_type": APITypes.ztc}, + related_name="catalogus_configs", + null=False, + ) class Meta: ordering = ("domein", "rsin") diff --git a/src/open_inwoner/openzaak/tests/factories.py b/src/open_inwoner/openzaak/tests/factories.py index ef8cbc5d31..12dc867862 100644 --- a/src/open_inwoner/openzaak/tests/factories.py +++ b/src/open_inwoner/openzaak/tests/factories.py @@ -74,6 +74,7 @@ class CatalogusConfigFactory(factory.django.DjangoModelFactory): url = factory.Faker("url") domein = factory.Faker("pystr", max_chars=5) rsin = factory.Faker("pystr", max_chars=9) + service = factory.SubFactory(ServiceFactory, api_type=APITypes.ztc) class Meta: model = CatalogusConfig diff --git a/src/open_inwoner/openzaak/tests/test_migrations.py b/src/open_inwoner/openzaak/tests/test_migrations.py index 128ee04526..790c4bda04 100644 --- a/src/open_inwoner/openzaak/tests/test_migrations.py +++ b/src/open_inwoner/openzaak/tests/test_migrations.py @@ -1,7 +1,15 @@ +from django.db import DataError + from zgw_consumers.constants import APITypes -from open_inwoner.openzaak.tests.factories import ServiceFactory -from open_inwoner.utils.tests.test_migrations import TestSuccessfulMigrations +from open_inwoner.openzaak.tests.factories import ( + ServiceFactory, + ZGWApiGroupConfigFactory, +) +from open_inwoner.utils.tests.test_migrations import ( + TestFailingMigrations, + TestSuccessfulMigrations, +) class TestMultiZGWBackendMigrations(TestSuccessfulMigrations): @@ -66,3 +74,80 @@ def test_migration_0048_to_0051_multi_zgw_backend(self): expected, msg="Service config should have been moved to a new ZGWApiGroupConfig", ) + + +class RequiredServiceToCatalogusConfigMigrationsTestCase: + migrate_from = "0051_drop_root_zgw_fields" + migrate_to = "0052_add_catalogusconfig_service" + app = "openzaak" + + def setUp(self): + self.api_group_config = ZGWApiGroupConfigFactory() # Not affected by migrations + super().setUp() + + +class TestRequiredCatalogusConfigServiceHappyPath( + RequiredServiceToCatalogusConfigMigrationsTestCase, TestSuccessfulMigrations +): + def setUpBeforeMigration(self, apps): + CatalogusConfig = apps.get_model("openzaak", "CatalogusConfig") + CatalogusConfig.objects.create( + url="https://foobar.com", domein="foo", rsin="foo" + ) + + def test_migration_0051_to_0052_sets_service_from_only_api_group_config(self): + CatalogusConfig = self.apps.get_model("openzaak", "CatalogusConfig") + catalogus_config = CatalogusConfig.objects.all().get() + + self.assertEqual( + catalogus_config.service.pk, + self.api_group_config.ztc_service.pk, + ) + + +class TestRequiredCatalogusConfigServiceUnhappyPath( + RequiredServiceToCatalogusConfigMigrationsTestCase, TestFailingMigrations +): + def setUpBeforeMigration(self, apps): + super().setUpBeforeMigration(apps) + + # Create another API Group Config to simulate ambiguous service resolution + ZGWApiGroupConfig = apps.get_model("openzaak", "ZGWApiGroupConfig") + Service = apps.get_model("zgw_consumers", "Service") + OpenZaakConfig = apps.get_model("openzaak", "OpenZaakConfig") + CatalogusConfig = apps.get_model("openzaak", "CatalogusConfig") + + CatalogusConfig = apps.get_model("openzaak", "CatalogusConfig") + CatalogusConfig.objects.create( + url="https://foobar.com", domein="foo", rsin="foo" + ) + + catalogi_service = ServiceFactory(api_type=APITypes.ztc) + zaken_service = ServiceFactory(api_type=APITypes.zrc) + documenten_service = ServiceFactory(api_type=APITypes.drc) + forms_service = ServiceFactory(api_type=APITypes.orc) + + # Note we have to refetch the service instances here: the factories + # create models that differ from the between-migration models + # expected by this OpenZaakConfig + ZGWApiGroupConfig.objects.create( + open_zaak_config=OpenZaakConfig.objects.get( + id=self.api_group_config.open_zaak_config.id + ), + zrc_service=Service.objects.get(id=zaken_service.id), + ztc_service=Service.objects.get(id=catalogi_service.id), + drc_service=Service.objects.get(id=documenten_service.id), + form_service=Service.objects.get(id=forms_service.id), + ) + + def test_migration_0051_to_0052_raises_for_multiple_api_groups(self): + + with self.assertRaises(DataError) as cm: + self.attempt_migration() + + self.assertEqual( + str(cm.exception), + "Attempted to set CatalogusConfig.service using ZGWApiGroupConfig, but there" + " are multiple instances configured. Please (temporarily) ensure you have only a single" + " ZGWApiGroupConfig configured, then run this migration again.", + ) diff --git a/src/open_inwoner/openzaak/zgw_imports.py b/src/open_inwoner/openzaak/zgw_imports.py index 92b24d0719..aee64cdcdb 100644 --- a/src/open_inwoner/openzaak/zgw_imports.py +++ b/src/open_inwoner/openzaak/zgw_imports.py @@ -71,6 +71,7 @@ def import_catalog_configs() -> list[CatalogusConfig]: url=catalog.url, rsin=catalog.rsin or "", domein=catalog.domein, + service=client.configured_from, ) ) From 57d29d8bdb18a0cadc8b83a277ec1c46134cb1cb Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Mon, 10 Jun 2024 12:16:03 +0200 Subject: [PATCH 20/24] :wheelchair: [#2376] Set aria-expanded for desktop in BEM block --- .../templates/components/Header/Header.html | 4 +- .../Header/NavigationAuthenticated.html | 2 +- .../components/Header/PrimaryNavigation.html | 2 +- .../components/header/primary-navigation.js | 75 ++++++++++++------- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/open_inwoner/components/templates/components/Header/Header.html b/src/open_inwoner/components/templates/components/Header/Header.html index 45f8d477db..f622bee1e3 100644 --- a/src/open_inwoner/components/templates/components/Header/Header.html +++ b/src/open_inwoner/components/templates/components/Header/Header.html @@ -32,7 +32,7 @@ {# end of mobile header-menu with logo #} -
    +
    {% if cms_apps.products and request.user.is_authenticated or not config.hide_search_from_anonymous_users %} {% endif %} -