From febc6f7c762ac1acacc8ba60bfcb1ce917ff1612 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Thu, 30 Nov 2023 14:06:31 +0100 Subject: [PATCH 01/10] Delete organigram in model before exchanging --- src/onegov/agency/forms/agency.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/onegov/agency/forms/agency.py b/src/onegov/agency/forms/agency.py index 7b8fc08177..42414981b2 100644 --- a/src/onegov/agency/forms/agency.py +++ b/src/onegov/agency/forms/agency.py @@ -139,6 +139,9 @@ def update_model(self, model): del model.organigram if self.organigram.action == 'replace': if self.organigram.data: + # with this the new file gets a new storage id and therefore + # a browser refresh is not required as the html changes + del model.organigram model.organigram_file = self.organigram.file model.coordinates = self.coordinates.data if hasattr(self, 'access'): From a94b4f9d5f39882426457740db0a2d2165824e38 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Fri, 1 Dec 2023 06:38:06 +0100 Subject: [PATCH 02/10] Only delete if exists --- src/onegov/agency/forms/agency.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/onegov/agency/forms/agency.py b/src/onegov/agency/forms/agency.py index 42414981b2..2a8699f0aa 100644 --- a/src/onegov/agency/forms/agency.py +++ b/src/onegov/agency/forms/agency.py @@ -141,7 +141,8 @@ def update_model(self, model): if self.organigram.data: # with this the new file gets a new storage id and therefore # a browser refresh is not required as the html changes - del model.organigram + if model.organigram: + del model.organigram model.organigram_file = self.organigram.file model.coordinates = self.coordinates.data if hasattr(self, 'access'): From deea1c0b7d0e676557d68d6d2d912aa06f517164 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Fri, 1 Dec 2023 11:18:41 +0100 Subject: [PATCH 03/10] Revert "Only delete if exists" This reverts commit a94b4f9d5f39882426457740db0a2d2165824e38. --- src/onegov/agency/forms/agency.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/onegov/agency/forms/agency.py b/src/onegov/agency/forms/agency.py index 2a8699f0aa..42414981b2 100644 --- a/src/onegov/agency/forms/agency.py +++ b/src/onegov/agency/forms/agency.py @@ -141,8 +141,7 @@ def update_model(self, model): if self.organigram.data: # with this the new file gets a new storage id and therefore # a browser refresh is not required as the html changes - if model.organigram: - del model.organigram + del model.organigram model.organigram_file = self.organigram.file model.coordinates = self.coordinates.data if hasattr(self, 'access'): From 0af598bd1c14a4015787a2d6743db33523802cc2 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Fri, 1 Dec 2023 11:18:59 +0100 Subject: [PATCH 04/10] Revert "Delete organigram in model before exchanging" This reverts commit febc6f7c762ac1acacc8ba60bfcb1ce917ff1612. --- src/onegov/agency/forms/agency.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/onegov/agency/forms/agency.py b/src/onegov/agency/forms/agency.py index 42414981b2..7b8fc08177 100644 --- a/src/onegov/agency/forms/agency.py +++ b/src/onegov/agency/forms/agency.py @@ -139,9 +139,6 @@ def update_model(self, model): del model.organigram if self.organigram.action == 'replace': if self.organigram.data: - # with this the new file gets a new storage id and therefore - # a browser refresh is not required as the html changes - del model.organigram model.organigram_file = self.organigram.file model.coordinates = self.coordinates.data if hasattr(self, 'access'): From 6a0e232ae4d78c8f3a0d9352d34a23d25ccc3416 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Fri, 1 Dec 2023 11:22:25 +0100 Subject: [PATCH 05/10] Adds cache control to AgencyApp --- src/onegov/agency/app.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/onegov/agency/app.py b/src/onegov/agency/app.py index 8f439b8194..e7eea22b29 100644 --- a/src/onegov/agency/app.py +++ b/src/onegov/agency/app.py @@ -12,6 +12,7 @@ from onegov.agency.theme import AgencyTheme from onegov.api import ApiApp from onegov.core import utils +from onegov.core.framework import transaction_tween_factory from onegov.org import OrgApp from onegov.org.app import get_editor_asset as editor_assets from onegov.org.app import get_i18n_localedirs as get_org_i18n_localedirs @@ -169,3 +170,28 @@ def get_api_endpoints(): PersonApiEndpoint, MembershipApiEndpoint ] + + +@AgencyApp.tween_factory( + over=transaction_tween_factory +) +def cache_control_tween_factory( + app: AgencyApp, + handler: 'Callable[[AgencyRequest], Response]' +) -> 'Callable[[AgencyRequest], Response]': + + def cache_control_tween(request: AgencyRequest) -> 'Response': + """ Set headers and cookies for cache control. + + Makes sure, pages are not cached downstream when logged in by setting + the cache-control header accordingly. + + """ + + response = handler(request) + if request.is_logged_in: + response.headers.add('cache-control', 'no-store') + + return response + + return cache_control_tween From 44faf6403e218b612364d393693448068bd6f5ce Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Fri, 1 Dec 2023 11:25:13 +0100 Subject: [PATCH 06/10] Satisfy mypy --- src/onegov/agency/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/onegov/agency/app.py b/src/onegov/agency/app.py index e7eea22b29..d5222e7274 100644 --- a/src/onegov/agency/app.py +++ b/src/onegov/agency/app.py @@ -19,6 +19,12 @@ from onegov.org.app import get_redactor_asset as redactor_assets +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from collections.abc import Callable + from webob import Response + + class AgencyApp(OrgApp, ApiApp): request_class = AgencyRequest From df647dfd810bc2c7ec648b7e78ead956094ad321 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Mon, 4 Dec 2023 10:21:07 +0100 Subject: [PATCH 07/10] Revert "Adds cache control to AgencyApp" This reverts commit 6a0e232ae4d78c8f3a0d9352d34a23d25ccc3416. --- src/onegov/agency/app.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/onegov/agency/app.py b/src/onegov/agency/app.py index d5222e7274..3c765a914c 100644 --- a/src/onegov/agency/app.py +++ b/src/onegov/agency/app.py @@ -12,7 +12,6 @@ from onegov.agency.theme import AgencyTheme from onegov.api import ApiApp from onegov.core import utils -from onegov.core.framework import transaction_tween_factory from onegov.org import OrgApp from onegov.org.app import get_editor_asset as editor_assets from onegov.org.app import get_i18n_localedirs as get_org_i18n_localedirs @@ -176,28 +175,3 @@ def get_api_endpoints(): PersonApiEndpoint, MembershipApiEndpoint ] - - -@AgencyApp.tween_factory( - over=transaction_tween_factory -) -def cache_control_tween_factory( - app: AgencyApp, - handler: 'Callable[[AgencyRequest], Response]' -) -> 'Callable[[AgencyRequest], Response]': - - def cache_control_tween(request: AgencyRequest) -> 'Response': - """ Set headers and cookies for cache control. - - Makes sure, pages are not cached downstream when logged in by setting - the cache-control header accordingly. - - """ - - response = handler(request) - if request.is_logged_in: - response.headers.add('cache-control', 'no-store') - - return response - - return cache_control_tween From 62824427fb7452060e23f297b2613580e5e09bcd Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Mon, 4 Dec 2023 10:36:16 +0100 Subject: [PATCH 08/10] Introduce 'last-modified' response header for agency view --- src/onegov/agency/views/agencies.py | 18 ++++++++++++++++++ src/onegov/election_day/utils/common.py | 1 + 2 files changed, 19 insertions(+) diff --git a/src/onegov/agency/views/agencies.py b/src/onegov/agency/views/agencies.py index cf62716405..dfe11401df 100644 --- a/src/onegov/agency/views/agencies.py +++ b/src/onegov/agency/views/agencies.py @@ -19,6 +19,7 @@ from onegov.core.security import Public from onegov.core.templates import render_macro from onegov.core.utils import normalize_for_url +from onegov.election_day.utils import add_last_modified_header from onegov.form import Form from onegov.org.elements import Link from onegov.org.forms.generic import ChangeAdjacencyListUrlForm @@ -95,6 +96,10 @@ def view_agencies_sort(self, request): ) def view_agency(self, request): + @request.after + def add_headers(response): + add_last_modified_header(response, self.last_change) + return { 'title': self.title, 'agency': self, @@ -103,6 +108,19 @@ def view_agency(self, request): } +@AgencyApp.view( + model=ExtendedAgency, + request_method='HEAD', + permission=Public, +) +def view_agency_head(self, request): + """ Get the last modification date. """ + + @request.after + def add_headers(response) -> None: + add_last_modified_header(response, self.last_modified) + + @AgencyApp.html( model=ExtendedAgency, template='custom_sort.pt', diff --git a/src/onegov/election_day/utils/common.py b/src/onegov/election_day/utils/common.py index eac1eb39da..d0fdf16d69 100644 --- a/src/onegov/election_day/utils/common.py +++ b/src/onegov/election_day/utils/common.py @@ -56,6 +56,7 @@ def __setitem__(self, key: _KT, value: _VT) -> None: super().move_to_end(key) +# TODO: move to general utils def add_last_modified_header( response: 'Response', last_modified: 'datetime | None' From f9d61fbdfa58dd5ed9e7c84e108585d047d25785 Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Thu, 7 Dec 2023 08:42:14 +0100 Subject: [PATCH 09/10] Move head view to file --- src/onegov/agency/views/agencies.py | 18 ------------------ src/onegov/core/utils.py | 15 +++++++++++++++ src/onegov/file/integration.py | 18 +++++++++++++++++- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/onegov/agency/views/agencies.py b/src/onegov/agency/views/agencies.py index dfe11401df..cf62716405 100644 --- a/src/onegov/agency/views/agencies.py +++ b/src/onegov/agency/views/agencies.py @@ -19,7 +19,6 @@ from onegov.core.security import Public from onegov.core.templates import render_macro from onegov.core.utils import normalize_for_url -from onegov.election_day.utils import add_last_modified_header from onegov.form import Form from onegov.org.elements import Link from onegov.org.forms.generic import ChangeAdjacencyListUrlForm @@ -96,10 +95,6 @@ def view_agencies_sort(self, request): ) def view_agency(self, request): - @request.after - def add_headers(response): - add_last_modified_header(response, self.last_change) - return { 'title': self.title, 'agency': self, @@ -108,19 +103,6 @@ def add_headers(response): } -@AgencyApp.view( - model=ExtendedAgency, - request_method='HEAD', - permission=Public, -) -def view_agency_head(self, request): - """ Get the last modification date. """ - - @request.after - def add_headers(response) -> None: - add_last_modified_header(response, self.last_modified) - - @AgencyApp.html( model=ExtendedAgency, template='custom_sort.pt', diff --git a/src/onegov/core/utils.py b/src/onegov/core/utils.py index a56f20a7a3..70b27fdeaf 100644 --- a/src/onegov/core/utils.py +++ b/src/onegov/core/utils.py @@ -44,6 +44,7 @@ if TYPE_CHECKING: from _typeshed import SupportsRichComparison from collections.abc import Callable, Collection, Iterator + from datetime import datetime from fs.base import FS, SubFS from re import Match from sqlalchemy import Column @@ -1181,3 +1182,17 @@ def batched( return yield batch + + +# copied from election_day/common.py +def add_last_modified_header( + response: 'Response', + last_modified: 'datetime | None' +) -> None: + """ Adds the give date to the response as Last-Modified header. """ + + if last_modified: + response.headers.add( + 'Last-Modified', + last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT") + ) diff --git a/src/onegov/file/integration.py b/src/onegov/file/integration.py index 2703199d0a..b288e0aab5 100644 --- a/src/onegov/file/integration.py +++ b/src/onegov/file/integration.py @@ -14,7 +14,8 @@ from morepath import App from onegov.core.custom import json from onegov.core.security import Private, Public -from onegov.core.utils import is_valid_yubikey, yubikey_public_id +from onegov.core.utils import is_valid_yubikey, yubikey_public_id, \ + add_last_modified_header from onegov.file.collection import FileCollection from onegov.file.errors import AlreadySignedError from onegov.file.errors import InvalidTokenError @@ -429,6 +430,7 @@ def configure_depot_tween_factory( app: DepotApp, handler: 'Callable[[CoreRequest], Response]' ) -> 'Callable[[CoreRequest], Response]': + print('*** tschupre configure depot tween') assert app.has_database_connection, "This module requires a db backed app." @@ -443,6 +445,7 @@ def render_depot_file( file: 'StoredFile', request: 'CoreRequest' ) -> 'Response': + print('*** tschupre render depot file') return request.get_response( FileServeApp(file, cache_max_age=3600 * 24 * 7)) @@ -479,11 +482,13 @@ def include_x_robots_tag_header(response: 'Response') -> None: @DepotApp.path(model=File, path='/storage/{id}') def get_file(app: DepotApp, id: str) -> File | None: + print('*** tschupre get file by id') return FileCollection(app.session()).by_id(id) @DepotApp.view(model=File, render=render_depot_file, permission=Public) def view_file(self: File, request: 'CoreRequest') -> 'StoredFile': + print('*** tschupre view_file') # only on browser 'force refresh' respond_with_alt_text(self, request) respond_with_caching_header(self, request) respond_with_x_robots_tag_header(self, request) @@ -500,6 +505,7 @@ def view_thumbnail( self: File, request: 'CoreRequest' ) -> 'StoredFile | Response': + print('*** tschupre view thumbnail') if request.view_name in ('small', 'medium'): size = request.view_name else: @@ -520,11 +526,17 @@ def view_thumbnail( @DepotApp.view(model=File, render=render_depot_file, permission=Public, request_method='HEAD') def view_file_head(self: File, request: 'CoreRequest') -> 'StoredFile': + print('*** tschupre view file head') @request.after def set_cache(response: 'Response') -> None: response.cache_control.max_age = 60 + @request.after + def add_headers(response: 'Response') -> None: + print('*** tschupre add last modified header') + add_last_modified_header(response, self.last_change) + return view_file(self, request) @@ -534,6 +546,7 @@ def view_thumbnail_head( self: File, request: 'CoreRequest' ) -> 'StoredFile | Response': + print('*** tschupre view thumbnail head') @request.after def set_cache(response: 'Response') -> None: @@ -545,6 +558,7 @@ def set_cache(response: 'Response') -> None: @DepotApp.view(model=File, name='note', request_method='POST', permission=Private) def handle_note_update(self: File, request: 'CoreRequest') -> None: + print('*** tschupre handle note update') request.assert_valid_csrf_token() note = request.POST.get('note') @@ -562,6 +576,7 @@ def handle_note_update(self: File, request: 'CoreRequest') -> None: @DepotApp.view(model=File, name='rename', request_method='POST', permission=Private) def handle_rename(self: File, request: 'CoreRequest') -> None: + print('*** tschupre handle rename') request.assert_valid_csrf_token() name = request.POST.get('name') @@ -596,6 +611,7 @@ def delete_file(self: File, request: 'CoreRequest') -> None: New tokens can be acquired through ``request.new_csrf_token``. """ + print('*** tschupre delete file') request.assert_valid_csrf_token() if self.signed: From 8135f426779bebae071981b7d21025d77675eb2f Mon Sep 17 00:00:00 2001 From: Reto Tschuppert Date: Thu, 7 Dec 2023 14:49:40 +0100 Subject: [PATCH 10/10] Always create a new organigram if the image changes --- src/onegov/people/models/agency.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/onegov/people/models/agency.py b/src/onegov/people/models/agency.py index dee9099488..3a8523a582 100644 --- a/src/onegov/people/models/agency.py +++ b/src/onegov/people/models/agency.py @@ -96,14 +96,10 @@ def organigram_file(self, value): normalize_for_url(self.title), extension_for_content_type(content_type_from_fileobj(value)) ) - if self.organigram: - self.organigram.reference = as_fileintent(value, filename) - self.organigram.name = filename - else: - organigram = AgencyOrganigram(id=random_token()) - organigram.reference = as_fileintent(value, filename) - organigram.name = filename - self.organigram = organigram + organigram = AgencyOrganigram(id=random_token()) + organigram.reference = as_fileintent(value, filename) + organigram.name = filename + self.organigram = organigram def add_person(self, person_id, title, **kwargs): """ Appends a person to the agency with the given title. """