From 5ab89178683a73144cf69b27ff3c4a821909d721 Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Wed, 8 Nov 2023 01:00:26 +0100 Subject: [PATCH 1/4] 308 Investigate, upgrade docker to Python 3.11, upgrade Django to 4.2 --- Makefile | 1 + docker-compose.yml | 2 +- docker/backend/Dockerfile | 7 +++-- examples/requirements/common.in | 2 +- examples/requirements/django_4_1.txt | 2 +- examples/requirements/django_4_2.in | 15 +++++++++++ examples/simple/settings/docker.py | 26 ++++++++++++++++--- .../simple/settings/local_settings_docker.py | 2 ++ 8 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 examples/requirements/django_4_2.in create mode 100644 examples/simple/settings/local_settings_docker.py diff --git a/Makefile b/Makefile index 3cb1fa165..779d0620e 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,7 @@ pip-compile: docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile django_3_2.in docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile django_4_0.in docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile django_4_1.in + docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile django_4_2.in docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile djangocms_3_4_3.in docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile djangorestframework.in docker-compose -f docker-compose.yml exec -w /backend/examples/requirements/ backend pip-compile docs.in diff --git a/docker-compose.yml b/docker-compose.yml index 16f944ccb..f7022cc6d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: postgresql: - image: postgres + image: postgres:14-bullseye restart: always # network_mode: "host" volumes: diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 5309557ff..082814037 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/python:3.10-slim +FROM docker.io/python:3.11-slim ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED 1 @@ -12,8 +12,7 @@ RUN apt-get update && \ nano \ chromium \ graphviz \ - libpq-dev \ - python3.9 + libpq-dev RUN pip install pip --upgrade RUN pip install virtualenv @@ -21,7 +20,7 @@ RUN pip install virtualenv RUN mkdir /backend WORKDIR /backend ADD examples/requirements/ /backend/requirements/ -RUN pip install -r /backend/requirements/django_4_1.in +RUN pip install -r /backend/requirements/django_4_2.in #RUN python -c "import geckodriver_autoinstaller; print(geckodriver_autoinstaller.install())" RUN python -c "from chromedriver_py import binary_path; print(binary_path)" COPY . /backend/ diff --git a/examples/requirements/common.in b/examples/requirements/common.in index dc8cba40d..ce2459a9d 100644 --- a/examples/requirements/common.in +++ b/examples/requirements/common.in @@ -17,7 +17,7 @@ pickleshare>=0.5 Pillow>=4.2.1 pluggy>=0.7.1 ptyprocess>=0.5 -psycopg2-binary>=2.8.2,<2.9 +psycopg2-binary Pygments>=2.0.2 pytz>=2019.1 #requests==2.8.1 diff --git a/examples/requirements/django_4_1.txt b/examples/requirements/django_4_1.txt index 572b2f003..246184d74 100644 --- a/examples/requirements/django_4_1.txt +++ b/examples/requirements/django_4_1.txt @@ -198,7 +198,7 @@ pluggy==1.0.0 # tox pre-commit==2.20.0 # via -r style_checkers.in -psycopg2-binary==2.8.6 +psycopg2-binary==2.9.9 # via -r common.in ptyprocess==0.7.0 # via diff --git a/examples/requirements/django_4_2.in b/examples/requirements/django_4_2.in new file mode 100644 index 000000000..22daf96b5 --- /dev/null +++ b/examples/requirements/django_4_2.in @@ -0,0 +1,15 @@ +-r common.in +-r test.in +-r style_checkers.in +-r feincms_1_20.in + +Django>=4.2,<5.0 +django-admin-tools>=0.8.0 +django-autoslug>=1.9.6 +django-ckeditor>=5.8.0 +django-debug-toolbar>=2.1 +django-formtools>=2.2 +django-registration>=3.1.1 +django-simple-captcha>=0.5.12 +djangorestframework>=3.10 +easy-thumbnails>=2.7.0 diff --git a/examples/simple/settings/docker.py b/examples/simple/settings/docker.py index 26e93b7ae..95002e36c 100644 --- a/examples/simple/settings/docker.py +++ b/examples/simple/settings/docker.py @@ -1,6 +1,16 @@ from chromedriver_py import binary_path from selenium import webdriver +try: + from .local_settings_docker import DEBUG +except Exception as err: + DEBUG = True + +try: + from .local_settings_docker import DEBUG_TOOLBAR +except Exception as err: + DEBUG_TOOLBAR = True + from .bootstrap3_theme import * @@ -15,8 +25,8 @@ def gettext(s): PROJECT_DIR = project_dir -DEBUG = True -DEBUG_TOOLBAR = False + + DEBUG_TEMPLATE = True # TEMPLATE_DEBUG = True DEV = True @@ -57,7 +67,11 @@ def gettext(s): "page": "page.migrations", } -INTERNAL_IPS = ("127.0.0.1",) +if DEBUG: + import socket # only if you haven't already imported this + hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) + INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + ["127.0.0.1", "10.0.2.2"] + ALLOWED_HOSTS = ["*"] EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend" @@ -92,3 +106,9 @@ def gettext(s): CHROME_DRIVER_EXECUTABLE_PATH = binary_path # '/usr/bin/chromedriver' FIREFOX_BIN_PATH = "/usr/lib/firefox/firefox" PHANTOM_JS_EXECUTABLE_PATH = "" + +# Do not put any settings below this line +try: + from .local_settings_docker import * +except Exception as err: + pass diff --git a/examples/simple/settings/local_settings_docker.py b/examples/simple/settings/local_settings_docker.py new file mode 100644 index 000000000..def937719 --- /dev/null +++ b/examples/simple/settings/local_settings_docker.py @@ -0,0 +1,2 @@ +DEBUG = True +DEBUG_TOOLBAR = True From b7678b3f8ec8818f1419094f94be64ca96b3de12 Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Fri, 10 Nov 2023 23:48:15 +0100 Subject: [PATCH 2/4] Fix --- examples/simple/kitchen_sink/__init__.py | 0 examples/simple/kitchen_sink/forms.py | 12 ++++++++++++ .../templates/kitchen_sink/kitchen_sink.html | 15 +++++++++++++++ examples/simple/kitchen_sink/urls.py | 6 ++++++ examples/simple/kitchen_sink/views.py | 12 ++++++++++++ examples/simple/settings/base.py | 1 + examples/simple/urls/__init__.py | 1 + examples/simple/urls/class_based.py | 1 + src/fobi/base.py | 7 +++++-- src/fobi/contrib/apps/drf_integration/base.py | 6 ++++-- src/fobi/helpers.py | 10 ++++++++-- src/fobi/templatetags/fobi_tags.py | 12 +++++++----- src/fobi/utils.py | 16 ++++++++++++---- src/fobi/views/class_based.py | 11 +++++++++-- src/fobi/views/function_based.py | 7 ++++++- 15 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 examples/simple/kitchen_sink/__init__.py create mode 100644 examples/simple/kitchen_sink/forms.py create mode 100644 examples/simple/kitchen_sink/templates/kitchen_sink/kitchen_sink.html create mode 100644 examples/simple/kitchen_sink/urls.py create mode 100644 examples/simple/kitchen_sink/views.py diff --git a/examples/simple/kitchen_sink/__init__.py b/examples/simple/kitchen_sink/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/simple/kitchen_sink/forms.py b/examples/simple/kitchen_sink/forms.py new file mode 100644 index 000000000..7e05122b0 --- /dev/null +++ b/examples/simple/kitchen_sink/forms.py @@ -0,0 +1,12 @@ +from django import forms + + +class KitchenSinkForm(forms.Form): + choices = forms.MultipleChoiceField( + choices=[ + ("a", "A"), + ("b", "B"), + ("c", "C"), + ], + widget=forms.CheckboxSelectMultiple, + ) diff --git a/examples/simple/kitchen_sink/templates/kitchen_sink/kitchen_sink.html b/examples/simple/kitchen_sink/templates/kitchen_sink/kitchen_sink.html new file mode 100644 index 000000000..eac5d3d9f --- /dev/null +++ b/examples/simple/kitchen_sink/templates/kitchen_sink/kitchen_sink.html @@ -0,0 +1,15 @@ + + + + + Title + + + {{ message }} +
+ {% csrf_token %} + {{ form.as_p }} + +
+ + diff --git a/examples/simple/kitchen_sink/urls.py b/examples/simple/kitchen_sink/urls.py new file mode 100644 index 000000000..67baeb82a --- /dev/null +++ b/examples/simple/kitchen_sink/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from .views import KitchenSinkFormView + +urlpatterns = [ + path("", KitchenSinkFormView.as_view(), name="kitchen_sink"), +] diff --git a/examples/simple/kitchen_sink/views.py b/examples/simple/kitchen_sink/views.py new file mode 100644 index 000000000..dca3c8e55 --- /dev/null +++ b/examples/simple/kitchen_sink/views.py @@ -0,0 +1,12 @@ +from django.views.generic.edit import FormView +from django.urls import reverse, reverse_lazy + +from .forms import KitchenSinkForm + + +class KitchenSinkFormView(FormView): + template_name = "kitchen_sink/kitchen_sink.html" + form_class = KitchenSinkForm + + def get_success_url(self): + return f"{reverse('kitchen_sink')}?success" diff --git a/examples/simple/settings/base.py b/examples/simple/settings/base.py index da98a3ace..b947ac5d3 100644 --- a/examples/simple/settings/base.py +++ b/examples/simple/settings/base.py @@ -374,6 +374,7 @@ def gettext(s): # *********************************************************************** # Other project specific apps "foo", # Test app + "kitchen_sink", # Test app ] STATIC_ROOT = PROJECT_DIR(os.path.join("..", "..", "static")) diff --git a/examples/simple/urls/__init__.py b/examples/simple/urls/__init__.py index 3bb8cb099..9df4e4ede 100644 --- a/examples/simple/urls/__init__.py +++ b/examples/simple/urls/__init__.py @@ -1 +1,2 @@ from .class_based import * +# from .function_based import * diff --git a/examples/simple/urls/class_based.py b/examples/simple/urls/class_based.py index f45fae36f..7812b2d6d 100644 --- a/examples/simple/urls/class_based.py +++ b/examples/simple/urls/class_based.py @@ -82,6 +82,7 @@ url(r"^foo/", include("foo.urls")), # bar URLs: # url(r'^bar/', include('bar.urls')), + url(r"^kitchen-sink/", include("kitchen_sink.urls")), url(r"^$", TemplateView.as_view(template_name=fobi_home_template)), # django-fobi public forms contrib app: # url(r'^', include('fobi.contrib.apps.public_forms.urls')), diff --git a/src/fobi/base.py b/src/fobi/base.py index 38edb6c81..e8c43c8cf 100644 --- a/src/fobi/base.py +++ b/src/fobi/base.py @@ -3149,16 +3149,19 @@ def get_ignorable_form_fields(form_element_entries): # ***************************************************************************** -def get_cleaned_data(form, keys_to_remove=[], values_to_remove=[]): +def get_cleaned_data(form, keys_to_remove=None, values_to_remove=None): """Get cleaned data. Gets cleaned data, having the trash (fields without values) filtered out. :param form: + :param iterable keys_to_remove: :param iterable values_to_remove: :return dict: """ + if not keys_to_remove: + keys_to_remove = [] if not values_to_remove: values_to_remove = get_ignorable_form_values() @@ -3177,7 +3180,7 @@ def get_cleaned_data(form, keys_to_remove=[], values_to_remove=[]): return ordered_cleaned_data -def get_field_name_to_label_map(form, keys_to_remove=[], values_to_remove=[]): +def get_field_name_to_label_map(form, keys_to_remove=None, values_to_remove=None): """Get field name to label map. :param form: diff --git a/src/fobi/contrib/apps/drf_integration/base.py b/src/fobi/contrib/apps/drf_integration/base.py index b576e42e1..fd2944206 100644 --- a/src/fobi/contrib/apps/drf_integration/base.py +++ b/src/fobi/contrib/apps/drf_integration/base.py @@ -268,7 +268,7 @@ def get_processed_serializer_data(serializer, form_element_entries): def get_field_name_to_label_map( - serializer, keys_to_remove=[], values_to_remove=[] + serializer, keys_to_remove=None, values_to_remove=None ): """Get field name to label map. @@ -290,7 +290,7 @@ def get_field_name_to_label_map( return field_name_to_label_map -def get_cleaned_data(serializer, keys_to_remove=[], values_to_remove=[]): +def get_cleaned_data(serializer, keys_to_remove=None, values_to_remove=None): """Get cleaned data. Gets cleaned data, having the trash (fields without values) filtered @@ -301,6 +301,8 @@ def get_cleaned_data(serializer, keys_to_remove=[], values_to_remove=[]): :param iterable values_to_remove: :return dict: """ + if not keys_to_remove: + keys_to_remove = [] if not values_to_remove: values_to_remove = get_ignorable_form_values() diff --git a/src/fobi/helpers.py b/src/fobi/helpers.py index 7ace2fe17..5a9aa0bbd 100644 --- a/src/fobi/helpers.py +++ b/src/fobi/helpers.py @@ -140,7 +140,7 @@ def map_field_name_to_label(form): ) -def clean_dict(source, keys=[], values=[]): +def clean_dict(source, keys=None, values=None): """Removes given keys and values from dictionary. :param dict source: @@ -148,6 +148,10 @@ def clean_dict(source, keys=[], values=[]): :param iterable values: :return dict: """ + if not keys: + keys = [] + if not values: + values = [] dict_data = {} for key, value in source.items(): if (key not in keys) and (value not in values): @@ -338,13 +342,15 @@ def extract_file_path(name): # ***************************************************************************** -def get_registered_models(ignore=[]): +def get_registered_models(ignore=None): """Gets registered models as list. :param iterable ignore: Ignore the following content types (should be in ``app_label.model`` format (example ``auth.User``). :return list: """ + if not ignore: + ignore = [] get_models = django.apps.apps.get_models registered_models = [ diff --git a/src/fobi/templatetags/fobi_tags.py b/src/fobi/templatetags/fobi_tags.py index 04eb6fc9e..36a7cc274 100644 --- a/src/fobi/templatetags/fobi_tags.py +++ b/src/fobi/templatetags/fobi_tags.py @@ -411,12 +411,14 @@ class FormFieldType(object): is_radio = False is_textarea = False - def __init__(self, properties=[]): + def __init__(self, properties=None): """Constructor. By default all of them are false. Provide only property names that should be set to True. """ + if not properties: + properties = [] for prop in properties: setattr(self, prop, True) @@ -434,13 +436,13 @@ def render(self, context): field = self.field.resolve(context, True) properties = [] - if isinstance(field.field.widget, forms.CheckboxInput): - properties.append("is_checkbox") - if isinstance(field.field.widget, forms.CheckboxSelectMultiple): properties.append("is_checkbox_multiple") - if isinstance(field.field.widget, forms.RadioSelect): + elif isinstance(field.field.widget, forms.CheckboxInput): + properties.append("is_checkbox") + + elif isinstance(field.field.widget, forms.RadioSelect): properties.append("is_radio") res = FormFieldType(properties) diff --git a/src/fobi/utils.py b/src/fobi/utils.py index 8e9031d36..6a277d27b 100644 --- a/src/fobi/utils.py +++ b/src/fobi/utils.py @@ -301,7 +301,7 @@ def get_user_handler_plugins( handler_plugin_registry, user, exclude_used_singles=False, - used_handler_plugin_uids=[], + used_handler_plugin_uids=None, ): """Get list of plugins allowed for user. @@ -313,6 +313,8 @@ def get_user_handler_plugins( :param list used_handler_plugin_uids: :return list: """ + if not used_handler_plugin_uids: + used_handler_plugin_uids = [] user_handler_plugins = get_user_plugins( get_allowed_handler_plugin_uids, get_registered_handler_plugins, @@ -410,7 +412,7 @@ def get_allowed_form_handler_plugin_uids(user): def get_user_form_handler_plugins( - user, exclude_used_singles=False, used_form_handler_plugin_uids=[] + user, exclude_used_singles=False, used_form_handler_plugin_uids=None ): """Get list of plugins allowed for user. @@ -419,6 +421,8 @@ def get_user_form_handler_plugins( :param list used_form_handler_plugin_uids: :return list: """ + if not used_form_handler_plugin_uids: + used_form_handler_plugin_uids = [] return get_user_handler_plugins( get_allowed_form_handler_plugin_uids, get_registered_form_handler_plugins, @@ -431,7 +435,7 @@ def get_user_form_handler_plugins( # def get_user_form_handler_plugins(user, # exclude_used_singles=False, -# used_form_handler_plugin_uids=[]): +# used_form_handler_plugin_uids=None): # """Get list of plugins allowed for user. # # :param django.contrib.auth.models.User user: @@ -439,6 +443,8 @@ def get_user_form_handler_plugins( # :param list used_form_handler_plugin_uids: # :return list: # """ +# if not used_form_handler_plugin_uids: +# used_form_handler_plugin_uids = [] # user_form_handler_plugins = get_user_plugins( # get_allowed_form_handler_plugin_uids, # get_registered_form_handler_plugins, @@ -510,7 +516,7 @@ def get_allowed_form_wizard_handler_plugin_uids(user): def get_user_form_wizard_handler_plugins( - user, exclude_used_singles=False, used_form_wizard_handler_plugin_uids=[] + user, exclude_used_singles=False, used_form_wizard_handler_plugin_uids=None ): """Get list of plugins allowed for user. @@ -519,6 +525,8 @@ def get_user_form_wizard_handler_plugins( :param list used_form_wizard_handler_plugin_uids: :return list: """ + if not used_form_wizard_handler_plugin_uids: + used_form_wizard_handler_plugin_uids = [] return get_user_handler_plugins( get_allowed_form_wizard_handler_plugin_uids, get_registered_form_wizard_handler_plugins, diff --git a/src/fobi/views/class_based.py b/src/fobi/views/class_based.py index 322e61242..1265222f4 100644 --- a/src/fobi/views/class_based.py +++ b/src/fobi/views/class_based.py @@ -1586,6 +1586,10 @@ def get_template_names(self): else: theme = self.theme template_name = theme.view_form_entry_template + + if DEBUG: + logger.debug(f"template_name: {template_name}") + return [template_name] def get_essential_objects( @@ -1603,7 +1607,6 @@ def get_essential_objects( form_element_entries=form_element_entries, request=request, ) - return ( form_element_entries, form_cls, @@ -1670,7 +1673,7 @@ def get(self, request, *args, **kwargs): # In debug mode, try to identify possible problems. if DEBUG: - form.as_p() + logger.debug(form.as_p()) else: try: form.as_p() @@ -1680,6 +1683,10 @@ def get(self, request, *args, **kwargs): theme = get_theme(request=request, as_instance=True) theme.collect_plugin_media(form_element_entries) + if DEBUG: + logger.debug(f"theme.form_view_ajax: {theme.form_view_ajax}") + logger.debug(f"theme.form_snippet_template_name: {theme.form_snippet_template_name}") + return self.render_to_response( self.get_context_data( form=form, diff --git a/src/fobi/views/function_based.py b/src/fobi/views/function_based.py index 861361545..19c23368f 100644 --- a/src/fobi/views/function_based.py +++ b/src/fobi/views/function_based.py @@ -2317,7 +2317,7 @@ def view_form_entry(request, form_entry_slug, theme=None, template_name=None): # In debug mode, try to identify possible problems. if DEBUG: - form.as_p() + logger.debug(form.as_p()) else: try: form.as_p() @@ -2337,6 +2337,11 @@ def view_form_entry(request, form_entry_slug, theme=None, template_name=None): if not template_name: template_name = theme.view_form_entry_template + if DEBUG: + logger.debug(f"template_name: {template_name}") + logger.debug(f"theme.form_view_ajax: {theme.form_view_ajax}") + logger.debug(f"theme.form_snippet_template_name: {theme.form_snippet_template_name}") + return render(request, template_name, context) From 6b36b7d6e36db044ce43e50693cf99d4b860f5d0 Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Sat, 11 Nov 2023 21:40:55 +0100 Subject: [PATCH 3/4] Debuggingg --- src/fobi/tests/test_core.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/fobi/tests/test_core.py b/src/fobi/tests/test_core.py index 642d20e0d..4ef25a498 100644 --- a/src/fobi/tests/test_core.py +++ b/src/fobi/tests/test_core.py @@ -1,4 +1,5 @@ import datetime +import logging from django.test import RequestFactory, TestCase from django.urls import reverse @@ -23,6 +24,8 @@ __license__ = "GPL 2.0/LGPL 2.1" __all__ = ("FobiCoreTest",) +LOGGER = logging.getLogger(__name__) + class FobiCoreTest(TestCase): """Tests of django-fobi core functionality.""" @@ -92,8 +95,10 @@ def _test_form_action_url(self, form_entry, action_url): if form.is_valid(): form.save() saved = True - except Exception: - pass + else: + LOGGER.debug(form.errors) + except Exception as err: + LOGGER.error(err) return saved From 61ffa867a9d7296fe813be997777125484ddeb56 Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Sun, 12 Nov 2023 01:11:55 +0100 Subject: [PATCH 4/4] Fixes --- src/fobi/tests/test_core.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/fobi/tests/test_core.py b/src/fobi/tests/test_core.py index 4ef25a498..b9a4f5ddf 100644 --- a/src/fobi/tests/test_core.py +++ b/src/fobi/tests/test_core.py @@ -89,6 +89,8 @@ def _test_form_action_url(self, form_entry, action_url): ) form = FormEntryForm(request.POST, request=request, instance=form_entry) + LOGGER.exception(f"action_url: {action_url}") + saved = False try: @@ -96,9 +98,9 @@ def _test_form_action_url(self, form_entry, action_url): form.save() saved = True else: - LOGGER.debug(form.errors) + LOGGER.exception(form.errors) except Exception as err: - LOGGER.error(err) + LOGGER.exception(err) return saved @@ -141,19 +143,19 @@ def test_05_action_url(self): # External URL, OK test saved = self._test_form_action_url( - form_entry, "http://delusionalinsanity.com/portfolio/" + form_entry, "https://github.com/barseghyanartur/django-fobi/" ) self.assertTrue(saved) # External URL, fail test saved = self._test_form_action_url( - form_entry, "http://delusionalinsanity.com2/portfolio/" + form_entry, "https://github.com2/barseghyanartur/django-fobi/" ) self.assertTrue(not saved) # External URL, fail test saved = self._test_form_action_url( - form_entry, "http://delusionalinsanity2.com/portfolio/" + form_entry, "https://github.com/barseghyanartur/django-fobi-i-do-not-exist/" ) self.assertTrue(not saved)