diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8bad880444..7bacec5447 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -2,7 +2,8 @@ . .githooks/toolkit -PRE_COMMIT_CHECKS=${PRE_COMMIT_CHECKS-"flake8 isort eslint headers"} +#PRE_COMMIT_CHECKS=${PRE_COMMIT_CHECKS-"flake8 isort eslint headers"} +PRE_COMMIT_CHECKS=${PRE_COMMIT_CHECKS-"flake8 isort headers"} hook_check_isort() { git diff HEAD~1 --name-only --diff-filter=MARC | { grep '.py$' || true; } | xargs --no-run-if-empty isort --check --diff --atomic diff --git a/.githooks/pre-push b/.githooks/pre-push index f425347ac7..0a98be355e 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -2,7 +2,8 @@ . .githooks/toolkit -PRE_PUSH_CHECKS=${PRE_PUSH_CHECKS-"flake8 isort eslint signature"} +#PRE_PUSH_CHECKS=${PRE_PUSH_CHECKS-"flake8 isort eslint signature"} +PRE_PUSH_CHECKS=${PRE_PUSH_CHECKS-"flake8 isort signature"} GIT_PARENT_BRANCH="${GIT_PARENT_BRANCH:-main}" hook_check_isort() { diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a7dfd3e72a..f2a7e27be5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,22 @@ == Version 2.3 Work in progress (not released -- this is a draft) == + UPGRADE NOTE : + - You should create a all new virtual environment based on Python 3.6+ + (eg: "mkvirtualenv -p /usr/bin/python3XX" if you use 'mkvirtualenv') + and populate it with "pip install -e .[mysql|pgsql]" of course. + - Execute the well known commands "migrate", "generatemedia" & "creme_populate". + + Users side : + ------------ + # The version of Django has been upgraded to 3.0. + + Developers side : ----------------- + - The version of Django is now "3.0". + You should probably read the following releases notes for Django versions : + - https://docs.djangoproject.com/en/3.2/releases/3.0/ + Non breaking changes : ---------------------- diff --git a/README.md b/README.md index 2c2737178c..e8d310771d 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ virtual env, in order to keep the old one working). - These python packages : (exact versions of Python packages are indicated in the 'setup.cfg' file) - Mandatory : - - Django 2.2 + - Django 3.0 - redis 3.4 - python-dateutil 2.8 - bleach 3.1 @@ -92,7 +92,7 @@ Installation with 'pip': - You should probably use "virtualenv" (on a Python >= 3.6). - Creme should be installed using 'pip install -e .' - About DB server : - - If you use MySQL, you must add the 'mysql' flag: + - If you use MySQL/MariaDB, you must add the 'mysql' flag: 'pip install .[mysql]' - For PostGreSQL, use 'pip install .[pgsql]' instead. - SQLite doesn't require a specific flag (see RECOMMENDATIONS). diff --git a/creme/activities/tests/test_activity.py b/creme/activities/tests/test_activity.py index cdefa3f4fd..e264a89cae 100644 --- a/creme/activities/tests/test_activity.py +++ b/creme/activities/tests/test_activity.py @@ -9,7 +9,7 @@ from django.forms import ModelMultipleChoiceField from django.forms.utils import ValidationError from django.urls import reverse -from django.utils.encoding import force_text +from django.utils.encoding import force_str # force_text from django.utils.timezone import now from django.utils.translation import gettext as _ @@ -2325,7 +2325,8 @@ def test_dl_ical(self): self.assertEqual('text/calendar', response['Content-Type']) self.assertEqual('attachment; filename=Calendar.ics', response['Content-Disposition']) - content = force_text(response.content) + # content = force_text(response.content) + content = force_str(response.content) self.assertStartsWith( content, 'BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//CremeCRM//CremeCRM//EN\n' diff --git a/creme/billing/tests/test_convert.py b/creme/billing/tests/test_convert.py index 3fa80dff28..6b2aac36c2 100644 --- a/creme/billing/tests/test_convert.py +++ b/creme/billing/tests/test_convert.py @@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType from django.db.models.query_utils import Q from django.urls import reverse -from django.utils.encoding import force_text +from django.utils.encoding import force_str # force_text from django.utils.timezone import now from django.utils.translation import gettext as _ @@ -214,8 +214,9 @@ def test_convert02_ajax(self): self.assertEqual(1, SalesOrder.objects.count()) self.assertEqual( - force_text(response.content), - SalesOrder.objects.first().get_absolute_url() + # force_text(response.content), + force_str(response.content), + SalesOrder.objects.first().get_absolute_url(), ) @skipIfCustomQuote diff --git a/creme/creme_core/apps.py b/creme/creme_core/apps.py index a065639801..b33194416a 100644 --- a/creme/creme_core/apps.py +++ b/creme/creme_core/apps.py @@ -156,6 +156,10 @@ def ready(self): for fname in ('app_label', 'model'): get_ct_field(fname).set_tags(viewable=False) + # NB: the original prefix with app's name => ugly choices for final users + # => use gettext+context had smooth translation instead + ContentType.__str__ = lambda this: this.name + class CremeAppConfig(AppConfig): # True => App can be used by some services diff --git a/creme/creme_core/management/commands/build_secret_key.py b/creme/creme_core/management/commands/build_secret_key.py index 022f791d6b..c6c1ac50e6 100644 --- a/creme/creme_core/management/commands/build_secret_key.py +++ b/creme/creme_core/management/commands/build_secret_key.py @@ -49,7 +49,7 @@ def handle(self, **options): from hashlib import sha256 from time import time - from django.utils.encoding import force_text + from django.utils.encoding import force_str # force_text choice = random.choice @@ -66,7 +66,8 @@ def handle(self, **options): random.seed( sha256( - f'{random.getstate()}{time()}{force_text(kb_seed)}'.encode('utf-8') + # f'{random.getstate()}{time()}{force_text(kb_seed)}'.encode('utf-8') + f'{random.getstate()}{time()}{force_str(kb_seed)}'.encode('utf-8') ).digest() ) diff --git a/creme/creme_core/management/commands/creme_uninstall.py b/creme/creme_core/management/commands/creme_uninstall.py index a9e958c357..ad688cbf6d 100644 --- a/creme/creme_core/management/commands/creme_uninstall.py +++ b/creme/creme_core/management/commands/creme_uninstall.py @@ -29,7 +29,7 @@ from django.db import DEFAULT_DB_ALIAS, connections from django.db.migrations.recorder import MigrationRecorder from django.dispatch import receiver -from django.utils.encoding import force_text +from django.utils.encoding import force_str # force_text from creme.creme_core.core.setting_key import setting_key_registry from creme.creme_core.gui.bricks import Brick @@ -470,7 +470,8 @@ def _delete_tables(self, app_config, app_label, verbosity): ' [KO] Original error: {error}.\n' 'Remaining tables:\n' '{models}\n'.format( - error=force_text(e), # PostGreSQL returns localized errors... + # error=force_text(e), # PostGreSQL returns localized errors... + error=force_str(e), # PostGreSQL returns localized errors... models='\n'.join(model._meta.db_table for model in models), ) ) diff --git a/creme/creme_core/management/commands/entity_factory.py b/creme/creme_core/management/commands/entity_factory.py index 64267c03e1..27d0fb966c 100644 --- a/creme/creme_core/management/commands/entity_factory.py +++ b/creme/creme_core/management/commands/entity_factory.py @@ -290,7 +290,6 @@ class Command(BaseCommand): SQL_OPTIMISERS = { 'django.db.backends.mysql': OptimizeMySQLContext, - 'django.db.backends.postgresql_psycopg2': OptimizePGSQLContext, 'django.db.backends.postgresql': OptimizePGSQLContext, # TODO: other DBRMS ? } diff --git a/creme/creme_core/management/commands/i18n_overload.py b/creme/creme_core/management/commands/i18n_overload.py index 9c6873640e..9f027ede9a 100644 --- a/creme/creme_core/management/commands/i18n_overload.py +++ b/creme/creme_core/management/commands/i18n_overload.py @@ -2,7 +2,7 @@ ################################################################################ # -# Copyright (c) 2009-2020 Hybird +# Copyright (c) 2009-2021 Hybird # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,7 @@ from django.apps import apps from django.conf import settings from django.core.management.base import BaseCommand, CommandError -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str APP_NAME = 'locale_overload' # TODO: can configure it with command options ?? @@ -181,7 +181,7 @@ def _overload_terms(self, language, verbosity, polib, file_name, *args): .localize(datetime.now()) \ .strftime('%Y-%m-%d %H:%M%z') - terms = [smart_text(arg) for arg in args] + terms = [smart_str(arg) for arg in args] entry_count = 0 for app_pofile in self._iter_pofiles(language, polib, file_name): diff --git a/creme/creme_core/middleware/exceptions.py b/creme/creme_core/middleware/exceptions.py index 7e04ead358..65ac510ebf 100644 --- a/creme/creme_core/middleware/exceptions.py +++ b/creme/creme_core/middleware/exceptions.py @@ -2,7 +2,7 @@ ################################################################################ # Creme is a free/open-source Customer Relationship Management software -# Copyright (C) 2009-2020 Hybird +# Copyright (C) 2009-2021 Hybird # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -25,7 +25,7 @@ from django.http import Http404, HttpResponse from django.shortcuts import render from django.utils.deprecation import MiddlewareMixin -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str # smart_text from creme.creme_core.core import exceptions as creme_exceptions @@ -41,7 +41,8 @@ class _AlternativeErrorMiddleware(MiddlewareMixin): def process_exception(self, request, exception): if self.error is None or isinstance(exception, self.error): - msg = smart_text(exception) + # msg = smart_text(exception) + msg = smart_str(exception) if request.is_ajax(): if self.log_ajax: @@ -52,9 +53,10 @@ def process_exception(self, request, exception): if self.template is None: return - return render(request, self.template, - {'error_message': msg}, status=self.status, - ) + return render( + request, self.template, + {'error_message': msg}, status=self.status, + ) class BadRequestMiddleware(_AlternativeErrorMiddleware): diff --git a/creme/creme_core/templatetags/creme_core_tags.py b/creme/creme_core/templatetags/creme_core_tags.py index fcb5d626ee..bc4bac38e4 100644 --- a/creme/creme_core/templatetags/creme_core_tags.py +++ b/creme/creme_core/templatetags/creme_core_tags.py @@ -31,7 +31,7 @@ from django.template import Template, TemplateSyntaxError from django.template.defaulttags import TemplateLiteral from django.template.library import token_kwargs -from django.utils.encoding import force_text +from django.utils.encoding import force_str # force_text from django.utils.html import escape, format_html_join from django.utils.safestring import mark_safe @@ -658,7 +658,8 @@ def render(self, context): logger.warning('jsondatablock tag do not accept custom "type" attribute') attrs = ''.join( - f' {k}="{escape(force_text(v.resolve(context)))}"' + # f' {k}="{escape(force_text(v.resolve(context)))}"' + f' {k}="{escape(force_str(v.resolve(context)))}"' for k, v in kwargs.items() ) diff --git a/creme/creme_core/tests/gui/test_field_printers.py b/creme/creme_core/tests/gui/test_field_printers.py index 2284246946..82bd905104 100644 --- a/creme/creme_core/tests/gui/test_field_printers.py +++ b/creme/creme_core/tests/gui/test_field_printers.py @@ -1119,7 +1119,8 @@ def test_registry_fk(self): self.assertEqual(str(casca.image), get_csv_val(casca, 'image', user)) self.assertEqual( - '

Casca's selfie

', + # '

Casca's selfie

', + '

Casca's selfie

', get_html_val(casca, 'image__description', user) ) self.assertEqual( @@ -1310,7 +1311,8 @@ def test_registry_credentials(self): get_html_val(judo, 'image', user) ) self.assertEqual( - '

Judo's selfie

', + # '

Judo's selfie

', + '

Judo's selfie

', get_html_val(judo, 'image__description', user) ) diff --git a/creme/creme_core/tests/models/test_custom_field.py b/creme/creme_core/tests/models/test_custom_field.py index 6a7e4c2ab3..ec3db73d0e 100644 --- a/creme/creme_core/tests/models/test_custom_field.py +++ b/creme/creme_core/tests/models/test_custom_field.py @@ -517,12 +517,13 @@ def test_multi_enum02(self): entity=orga, ) - with self.assertNumQueries(3): + # with self.assertNumQueries(3): + with self.assertNumQueries(2): cf_value.set_value_n_save([enum_value1, enum_value2]) self.assertSetEqual( {enum_value1, enum_value2}, - {*self.refresh(cf_value).value.all()} + {*self.refresh(cf_value).value.all()}, ) def test_delete(self): diff --git a/creme/creme_core/tests/views/test_job.py b/creme/creme_core/tests/views/test_job.py index 67ecc6bcee..aedab93a34 100644 --- a/creme/creme_core/tests/views/test_job.py +++ b/creme/creme_core/tests/views/test_job.py @@ -6,7 +6,7 @@ from django.db.models import Max from django.test.utils import override_settings from django.urls import reverse -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str from django.utils.formats import date_format from django.utils.timezone import localtime, now from django.utils.translation import gettext as _ @@ -51,7 +51,7 @@ def tearDown(self): # TODO: move to base class ? def _assertCount(self, response, found, count): - self.assertEqual(count, smart_text(response.content).count(found)) + self.assertEqual(count, smart_str(response.content).count(found)) def _build_enable_url(self, job): return reverse('creme_core__enable_job', args=(job.id,)) diff --git a/creme/creme_core/tests/views/test_listview.py b/creme/creme_core/tests/views/test_listview.py index bfa2007474..c564fc3f5d 100644 --- a/creme/creme_core/tests/views/test_listview.py +++ b/creme/creme_core/tests/views/test_listview.py @@ -5,6 +5,7 @@ from functools import partial from json import dumps as json_dump from random import shuffle +from urllib.parse import quote from xml.etree.ElementTree import tostring as html_tostring from django.conf import settings @@ -12,8 +13,8 @@ from django.db.models import Q from django.test.utils import override_settings from django.urls import reverse -from django.utils.encoding import force_text -from django.utils.http import urlquote +from django.utils.encoding import force_str # force_text +# from django.utils.http import urlquote from django.utils.timezone import now from django.utils.translation import gettext as _ from django.utils.translation import pgettext @@ -429,7 +430,8 @@ def post(selection): self.assertInHTML( f'', - force_text(response.content), + # force_text(response.content), + force_str(response.content), ) post('none') @@ -450,7 +452,8 @@ def get(selection): self.assertInHTML( f'', - force_text(response.content), + # force_text(response.content), + force_str(response.content), ) get('none') @@ -1003,7 +1006,8 @@ def test_ordering_fastmode_01(self): r'.creme_core_fakecontact.\..birthday. ASC( NULLS FIRST)?, ' r'.creme_core_fakecontact.\..last_name. ASC( NULLS FIRST)?, ' r'.creme_core_fakecontact.\..first_name. ASC( NULLS FIRST)?, ' - r'.creme_core_fakecontact.\..cremeentity_ptr_id. ASC( NULLS FIRST)? LIMIT' + # r'.creme_core_fakecontact.\..cremeentity_ptr_id. ASC( NULLS FIRST)? LIMIT' + r'.creme_core_fakecontact.\..cremeentity_ptr_id. ASC( NULLS FIRST)? LIMIT' ) @override_settings(FAST_QUERY_MODE_THRESHOLD=2) @@ -1014,7 +1018,8 @@ def test_ordering_fastmode_02(self): sql, r'ORDER BY' r' .creme_core_fakecontact.\..birthday. ASC( NULLS FIRST)?,' - r' .creme_core_fakecontact.\..cremeentity_ptr_id. ASC( NULLS FIRST)? LIMIT' + # r' .creme_core_fakecontact.\..cremeentity_ptr_id. ASC( NULLS FIRST)? LIMIT' + r' .creme_core_fakecontact.\..cremeentity_ptr_id. ASC( NULLS FIRST)? LIMIT' ) def test_efilter01(self): @@ -1176,8 +1181,10 @@ def test_header_buttons(self): dl_uri = data_hrefs[1] self.assertStartsWith(dl_uri, dl_url) self.assertIn(f'hfilter={hf.id}', dl_uri) - self.assertIn(f'&extra_q={urlquote(q_filter)}', dl_uri) - self.assertIn(f'&search-regular_field-phone={urlquote(searched_phone)}', dl_uri) + # self.assertIn(f'&extra_q={urlquote(q_filter)}', dl_uri) + self.assertIn(f'&extra_q={quote(q_filter)}', dl_uri) + # self.assertIn(f'&search-regular_field-phone={urlquote(searched_phone)}', dl_uri) + self.assertIn(f'&search-regular_field-phone={quote(searched_phone)}', dl_uri) dl_header_uri = data_hrefs[2] self.assertStartsWith(dl_header_uri, dl_url) diff --git a/creme/creme_core/tests/views/test_mass_export.py b/creme/creme_core/tests/views/test_mass_export.py index 72feedcf5a..dd06cf46b0 100644 --- a/creme/creme_core/tests/views/test_mass_export.py +++ b/creme/creme_core/tests/views/test_mass_export.py @@ -10,7 +10,7 @@ from django.db.models import Q from django.test.utils import override_settings from django.urls import reverse -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.formats import date_format from django.utils.timezone import localtime from django.utils.translation import gettext as _ @@ -205,7 +205,7 @@ def test_list_view_export_header(self): self.assertListEqual( [','.join(f'"{hfi.title}"' for hfi in cells)], - [force_text(line) for line in response.content.splitlines()], + [force_str(line) for line in response.content.splitlines()], ) self.assertFalse(HistoryLine.objects.exclude(id__in=existing_hline_ids)) @@ -232,7 +232,7 @@ def test_list_view_export01(self): # TODO: sort the relations/properties by their verbose_name ?? result = response.content.splitlines() - it = (force_text(line) for line in result) + it = (force_str(line) for line in result) self.assertEqual(next(it), ','.join(f'"{hfi.title}"' for hfi in hf.cells)) self.assertEqual(next(it), '"","Black","Jet","Bebop",""') self.assertIn( @@ -286,7 +286,7 @@ def test_list_view_export02(self): response = self.assertGET200(self._build_contact_dl_url(doc_type='scsv')) # TODO: sort the relations/properties by their verbose_name ?? - it = (force_text(line) for line in response.content.splitlines()) + it = (force_str(line) for line in response.content.splitlines()) self.assertEqual(next(it), ';'.join(f'"{hfi.title}"' for hfi in cells)) self.assertEqual(next(it), '"";"Black";"Jet";"Bebop";""') self.assertIn( @@ -340,7 +340,7 @@ def test_list_view_export04(self): self.assertTrue(user.has_perm_to_view(organisations['Swordfish'])) response = self.assertGET200(self._build_contact_dl_url()) - result = [*map(force_text, response.content.splitlines())] + result = [*map(force_str, response.content.splitlines())] self.assertEqual(result[1], '"","Black","Jet","",""') self.assertEqual(result[2], '"","Spiegel","Spike","Swordfish",""') self.assertEqual(result[3], '"","Wong","Edward","","is a girl"') @@ -361,7 +361,7 @@ def test_list_view_export05(self): response = self.assertGET200(self._build_contact_dl_url(hfilter_id=hf.id)) - result = [force_text(line) for line in response.content.splitlines()] + result = [force_str(line) for line in response.content.splitlines()] self.assertEqual(2, len(result)) self.assertEqual( result[1], @@ -401,7 +401,7 @@ def test_list_view_export06(self): ) response = self.assertGET200(self._build_contact_dl_url(hfilter_id=hf.id)) - it = (force_text(line) for line in response.content.splitlines()); next(it) + it = (force_str(line) for line in response.content.splitlines()); next(it) self.assertEqual(next(it), '"Black","Jet face","Jet\'s selfie"') @@ -435,7 +435,7 @@ def test_list_view_export07(self): list_url=FakeEmailCampaign.get_lv_absolute_url(), hfilter_id=hf.id, )) - result = [force_text(line) for line in response.content.splitlines()] + result = [force_str(line) for line in response.content.splitlines()] self.assertEqual(4, len(result)) self.assertEqual(result[1], '"Camp#1","ML#1/ML#2"') @@ -454,7 +454,7 @@ def test_list_view_export08(self): response = self.assertGET200(self._build_contact_dl_url()) - it = (force_text(line) for line in response.content.splitlines()) + it = (force_str(line) for line in response.content.splitlines()) self.assertEqual( next(it), ','.join( @@ -472,7 +472,7 @@ def test_extra_filter(self): self._build_contact_dl_url(extra_q=QSerializer().dumps(Q(last_name='Wong'))), ) - result = [force_text(line) for line in response.content.splitlines()] + result = [force_str(line) for line in response.content.splitlines()] self.assertEqual(2, len(result)) self.assertEqual('"","Wong","Edward","","is a girl"', result[1]) @@ -496,7 +496,7 @@ def test_list_view_export_with_filter01(self): list_url=FakeContact.get_lv_absolute_url(), efilter_id=efilter.id )) - result = [force_text(line) for line in response.content.splitlines()] + result = [force_str(line) for line in response.content.splitlines()] self.assertEqual(2, len(result)) self.assertEqual('"","Wong","Edward","","is a girl"', result[1]) @@ -628,7 +628,7 @@ def test_print_integer01(self): follow=True, ) - lines = {force_text(line) for line in response.content.splitlines()} + lines = {force_str(line) for line in response.content.splitlines()} self.assertIn('"Bebop","1000"', lines) self.assertIn('"Swordfish","20000"', lines) self.assertIn('"Redtail",""', lines) @@ -661,7 +661,7 @@ def test_print_integer02(self): follow=True, ) - lines = {force_text(line) for line in response.content.splitlines()} + lines = {force_str(line) for line in response.content.splitlines()} self.assertIn('"Bebop","{}"'.format(_('Percent')), lines) self.assertIn('"Swordfish","{}"'.format(_('Amount')), lines) @@ -723,7 +723,7 @@ def test_quick_search(self): '"123233","Spiegel","Spike"', ], # NB: slice to remove the header - [force_text(line) for line in response.content.splitlines()[1:]] + [force_str(line) for line in response.content.splitlines()[1:]] ) @override_settings(PAGE_SIZES=[10], DEFAULT_PAGE_SIZE_IDX=0) @@ -758,7 +758,7 @@ def test_sorting(self): '"123455","Black","Jet"', ], # NB: slice to remove the header - [force_text(line) for line in response.content.splitlines()[1:]] + [force_str(line) for line in response.content.splitlines()[1:]] ) def test_distinct(self): diff --git a/creme/creme_core/tests/views/test_mass_import.py b/creme/creme_core/tests/views/test_mass_import.py index ed56c381fe..82dbb42c74 100644 --- a/creme/creme_core/tests/views/test_mass_import.py +++ b/creme/creme_core/tests/views/test_mass_import.py @@ -8,7 +8,7 @@ from django.template.defaultfilters import slugify from django.test.utils import override_settings from django.urls import reverse -from django.utils.encoding import smart_text +from django.utils.encoding import smart_str from django.utils.timezone import now from django.utils.translation import gettext as _ from django.utils.translation import ngettext @@ -1579,7 +1579,7 @@ def _csv_to_list(response): return [ [i[1:-1] for i in line.split(separator)] - for line in smart_text(response.content).splitlines() + for line in smart_str(response.content).splitlines() ] def test_dl_errors01(self): diff --git a/creme/creme_core/utils/html.py b/creme/creme_core/utils/html.py index 599092ddcb..925c0f1e8a 100644 --- a/creme/creme_core/utils/html.py +++ b/creme/creme_core/utils/html.py @@ -22,7 +22,7 @@ import bleach from django.conf import settings -from django.utils.encoding import force_text +from django.utils.encoding import force_str # force_text from django.utils.html import mark_safe _AllowedAttributesDict = Dict[str, Union[Sequence[str], Callable[[str, str, str], bool]]] @@ -114,4 +114,5 @@ def sanitize_html(html: str, allow_external_img: bool = False) -> str: def escapejson(value: str) -> str: - return mark_safe(force_text(value).translate(JSON_ESCAPES)) + # return mark_safe(force_text(value).translate(JSON_ESCAPES)) + return mark_safe(force_str(value).translate(JSON_ESCAPES)) diff --git a/creme/creme_core/views/entity.py b/creme/creme_core/views/entity.py index f6a18284d3..387f7d1d56 100644 --- a/creme/creme_core/views/entity.py +++ b/creme/creme_core/views/entity.py @@ -32,10 +32,12 @@ from django.http import Http404, HttpResponse, HttpResponseBadRequest from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse +from django.utils.decorators import method_decorator from django.utils.html import format_html from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext +from django.views.decorators.clickjacking import xframe_options_sameorigin from .. import constants from ..auth import SUPERUSER_PERM @@ -107,6 +109,7 @@ def get_creme_entities_repr(request, entities_ids): ] +@method_decorator(xframe_options_sameorigin, name='dispatch') class HTMLFieldSanitizing(generic.base.EntityRelatedMixin, generic.CheckedView): """Used to show an HTML document in an