Skip to content

Commit

Permalink
Fixes regression with tom select. (No longer use inline js).
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrillkuettel committed Aug 31, 2024
1 parent 8d10be4 commit 4eea7d2
Show file tree
Hide file tree
Showing 7 changed files with 23 additions and 78 deletions.
3 changes: 0 additions & 3 deletions src/privatim/forms/consultation_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,6 @@ def __init__(
validators=[
validators.Optional(),
],
translations={
'placeholder': translate(_('Choose a canton...')),
}
)

files = UploadMultipleFilesWithORMSupport(
Expand Down
77 changes: 10 additions & 67 deletions src/privatim/forms/fields/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from sqlalchemy import select

from privatim.models.file import SearchableFile
from privatim.static import tom_select
from privatim.static import tom_select_js
from wtforms.utils import unset_value
from wtforms.validators import DataRequired
from wtforms.validators import InputRequired
Expand All @@ -14,16 +14,16 @@
from wtforms.widgets.core import Select
from werkzeug.datastructures import MultiDict
from privatim.forms.widgets.widgets import UploadWidget, UploadMultipleWidget
from privatim.i18n import _, translate
from privatim.i18n import _

from wtforms.fields import DateTimeLocalField as DateTimeLocalFieldBase
from privatim.models.file import GeneralFile
from operator import itemgetter
from markupsafe import Markup


from typing import Any, IO, Literal, TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from markupsafe import Markup
from sqlalchemy.orm import Session
from wtforms.fields.choices import SelectFieldBase
from collections.abc import Sequence
Expand Down Expand Up @@ -119,74 +119,19 @@ class TomSelectWidget(Select):
def __init__(
self,
multiple: bool = False,
translations: dict[str, str] | None = None,
) -> None:
super().__init__(multiple=multiple)
self.translations = translations or {}

def __call__(self, field: 'SelectFieldBase', **kwargs: Any) -> Markup:
def __call__(self, field: 'SelectFieldBase', **kwargs: Any) -> 'Markup':
if not kwargs.get('class'):
kwargs['class'] = 'searchable-select'
else:
kwargs['class'] += ' searchable-select'

placeholder = self.translations.get('not_found', _('Select...'))
placeholder = _('Select...')
kwargs['placeholder_'] = placeholder
kwargs['autocomplete_'] = 'off'

html = super(TomSelectWidget, self).__call__(field, **kwargs)
not_found_message = self.translations.get(
'not_found', translate(_('No Users Found'))
)
remove_message = translate(_('Remove this item'))

# We handle translations on the backend and inject them into
# JavaScript. We use this approach here just because this allows to
# use the existing i18n translation in python. There are only a few
# default messages in Tom Select. Implementing frontend
# i18n translations for just two messages would be overkill.

# You need to change Hash in cps_header.py if you change this
inline_js = Markup(f"""
<script type="text/javascript">
(function() {{
function initializeSearchableSelect(
uniqueId, translatedNotFound
) {{
let element = document.getElementById(uniqueId);
if (element) {{
let settings = {{
plugins: {{
remove_button:{{
title:'{remove_message}',
}}
}},
render: {{
option: function (data, escape) {{
return '<div>' + escape(data.text) + '</div>';
}},
no_results: function (data, escape) {{
return '<div class="no-results">' +
translatedNotFound + ' "'
+ escape(data.input) + '"</div>';
}},
}},
}};
new TomSelect(element, settings);
}}
}}
/* This schedules the initialization to happen on the next
animation frame, which is typically very soon after the DOM is
ready. DOMContentLoaded would take too long.
*/
requestAnimationFrame(function() {{
initializeSearchableSelect('{field.id}',
'{not_found_message}');
}});
}})();
</script>
""")
return inline_js + html
return super(TomSelectWidget, self).__call__(field, **kwargs)


class SearchableMultiSelectField(SelectMultipleField):
Expand All @@ -201,14 +146,13 @@ class SearchableMultiSelectField(SelectMultipleField):
def __init__(
self,
label: str,
translations: dict[str, str] | None = None,
**kwargs: Any
):
super().__init__(label, **kwargs)
self.widget = TomSelectWidget(multiple=True, translations=translations)
self.widget = TomSelectWidget(multiple=True)

def __call__(self, **kwargs: Any) -> Any:
tom_select.need()
tom_select_js.need()
return super().__call__(**kwargs)


Expand All @@ -220,16 +164,15 @@ class SearchableSelectField(SelectMultipleField):
def __init__(
self,
label: str,
translations: dict[str, str] | None = None,
**kwargs: Any
):
super().__init__(label, **kwargs)
self.widget = TomSelectWidget(
multiple=False, translations=translations
multiple=False
)

def __call__(self, **kwargs: Any) -> Any:
tom_select.need()
tom_select_js.need()
return super().__call__(**kwargs)


Expand Down
3 changes: 2 additions & 1 deletion src/privatim/layouts/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pyramid.renderers import get_renderer
from privatim.static import (bootstrap_css, bootstrap_js, tom_select_css,
comments_css, profile_css, sortable_custom,
custom_js, init_tiptap_editor)
custom_js, init_tiptap_editor, init_tom_select_js)
from pytz import timezone
import re
from datetime import date, datetime
Expand Down Expand Up @@ -38,6 +38,7 @@ def __init__(self, context: Any, request: 'IRequest') -> None:
self.year = date.today().year

init_tiptap_editor.need()
init_tom_select_js.need()
bootstrap_css.need()
bootstrap_js.need()
tom_select_css.need()
Expand Down
6 changes: 4 additions & 2 deletions src/privatim/static/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ def get_default_profile_pic_data() -> tuple[str, bytes]:
sortable_custom = js('custom/sortable_custom.js', depends=[jquery],
renderer=render_js_module)

custom_js = js('custom/custom.js', depends=[jquery])

tom_select_css = css('tom-select.min.css')
tom_select = js('tom_select.complete.min.js')
tom_select_js = js('tom_select.complete.min.js')
init_tom_select_js = js('init-tom-select.js')

custom_js = js('custom/custom.js', depends=[jquery])

bundle_js = js('tiptap.bundle.min.js')
init_tiptap_editor = js(
Expand Down
6 changes: 6 additions & 0 deletions src/privatim/static/js/init-tom-select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.searchable-select').forEach((el) => {
let settings = {};
new TomSelect(el, settings);
});
});
3 changes: 1 addition & 2 deletions src/subscribers/csp_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ def default_csp_directives(request: 'IRequest') -> dict[str, str]:
"img-src": "'self' data: blob:",
"object-src": "'self'",
# enable one inline script by hash TomSelectWidget
"script-src": "'self' blob: resource: "
"'sha256-wSu+A0D5LTY7IrsHQxOp9Ulz/skwI6R6Rbppxjht7Yg='",
"script-src": "'self' blob: resource: ",
"style-src": "'self' 'unsafe-inline'",
}

Expand Down
3 changes: 0 additions & 3 deletions tests/subscribers/test_csp_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def test_csp_header(pg_config):
"img-src 'self' data: blob:; "
"object-src 'self'; "
"script-src 'self' blob: resource: "
"'sha256-wSu+A0D5LTY7IrsHQxOp9Ulz/skwI6R6Rbppxjht7Yg='; "
"style-src 'self' 'unsafe-inline'"
)

Expand All @@ -42,7 +41,6 @@ def test_csp_header_sentry(pg_config):
"img-src 'self' data: blob:; "
"object-src 'self'; "
"script-src 'self' blob: resource: "
"'sha256-wSu+A0D5LTY7IrsHQxOp9Ulz/skwI6R6Rbppxjht7Yg='; "
"style-src 'self' 'unsafe-inline'; "
"report-uri https://sentry.io/api/22/security/?sentry_key=aa"
)
Expand All @@ -64,7 +62,6 @@ def test_csp_header_sentry(pg_config):
"img-src 'self' data: blob:; "
"object-src 'self'; "
"script-src 'self' blob: resource: "
"'sha256-wSu+A0D5LTY7IrsHQxOp9Ulz/skwI6R6Rbppxjht7Yg='; "
"style-src 'self' 'unsafe-inline'; "
"report-uri https://sentry.io/api/22/security/?sentry_key=aa"
)
Expand Down

0 comments on commit 4eea7d2

Please sign in to comment.