diff --git a/backend/aml/base_settings.py b/backend/aml/base_settings.py index cc7ce15a6..236717af1 100644 --- a/backend/aml/base_settings.py +++ b/backend/aml/base_settings.py @@ -18,6 +18,7 @@ # Workaround for deprecated ugettext_lazy in django-inline-actions import django from django.utils.translation import gettext_lazy + django.utils.translation.ugettext_lazy = gettext_lazy logger = logging.getLogger(__name__) @@ -30,70 +31,71 @@ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv("AML_SECRET_KEY", 'topsecret') +SECRET_KEY = os.getenv("AML_SECRET_KEY", "topsecret") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv('AML_DEBUG', '') != 'False' +DEBUG = os.getenv("AML_DEBUG", "") != "False" ALLOWED_HOSTS = os.getenv("AML_ALLOWED_HOSTS", "localhost").split(",") # Application definition INSTALLED_APPS = [ - 'admin_interface', - 'modeltranslation', # Must be before django.contrib.admin - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.humanize', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'nested_admin', - 'inline_actions', - 'django_markup', - 'corsheaders', - 'experiment', - 'image', - 'participant', - 'result', - 'session', - 'section', - 'theme', - 'question' + "admin_interface", + "modeltranslation", # Must be before django.contrib.admin + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.humanize", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_select2", + "nested_admin", + "inline_actions", + "django_markup", + "corsheaders", + "experiment", + "image", + "participant", + "result", + "session", + "section", + "theme", + "question", ] MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "corsheaders.middleware.CorsMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'aml.urls' +ROOT_URLCONF = "aml.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'aml.wsgi.application' +WSGI_APPLICATION = "aml.wsgi.application" # Password validation @@ -101,16 +103,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -120,7 +122,7 @@ # LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'Europe/Amsterdam' +TIME_ZONE = "Europe/Amsterdam" USE_I18N = True @@ -128,7 +130,7 @@ USE_TZ = True -MODELTRANSLATION_LANGUAGES = ('en', 'nl', 'pt') +MODELTRANSLATION_LANGUAGES = ("en", "nl", "pt") # Increase django limits for large data sets # A request timeout should be set in the webserver @@ -141,28 +143,27 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" # Added to run : python manage.py collectstatic -STATIC_ROOT = os.path.join(BASE_DIR, 'static') -MEDIA_ROOT = os.path.join(BASE_DIR, 'upload') -MEDIA_URL = '/upload/' +STATIC_ROOT = os.path.join(BASE_DIR, "static") +MEDIA_ROOT = os.path.join(BASE_DIR, "upload") +MEDIA_URL = "/upload/" # Geoip service LOCATION_PROVIDER = os.getenv("AML_LOCATION_PROVIDER", "") # From mail address, used for sending mails -FROM_MAIL = 'name@example.com' +FROM_MAIL = "name@example.com" CONTACT_MAIL = FROM_MAIL # Target url participants will be redirected to after reloading their user-session -RELOAD_PARTICIPANT_TARGET = 'https://app.amsterdammusiclab.nl' -HOMEPAGE = 'https://www.amsterdammusiclab.nl' +RELOAD_PARTICIPANT_TARGET = "https://app.amsterdammusiclab.nl" +HOMEPAGE = "https://www.amsterdammusiclab.nl" # CORS origin white list from .env CORS_ORIGIN_WHITELIST = os.getenv( - "AML_CORS_ORIGIN_WHITELIST", - "http://localhost:3000,http://127.0.0.1:3000,{}".format(HOMEPAGE) + "AML_CORS_ORIGIN_WHITELIST", "http://localhost:3000,http://127.0.0.1:3000,{}".format(HOMEPAGE) ).split(",") @@ -171,19 +172,19 @@ CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_HEADERS = list(default_headers) + [ - 'sentry-trace', - 'baggage', + "sentry-trace", + "baggage", ] -CSRF_TRUSTED_ORIGINS = [os.getenv('CSRF_TRUSTED_ORIGINS')] +CSRF_TRUSTED_ORIGINS = [os.getenv("CSRF_TRUSTED_ORIGINS")] -SESSION_SAVE_EVERY_REQUEST = False # Do not set to True, because it will break session-based participant_id +SESSION_SAVE_EVERY_REQUEST = False # Do not set to True, because it will break session-based participant_id CSRF_USE_SESSIONS = False -LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')] +LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")] -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" if os.getenv("SENTRY_DSN"): sentry_sdk.init( @@ -201,10 +202,6 @@ else: logger.info("SENTRY_DSN is not defined. Skipping Sentry initialization.") -MARKUP_SETTINGS = { - 'markdown': { - 'safe_mode': False - } -} +MARKUP_SETTINGS = {"markdown": {"safe_mode": False}} -SUBPATH = os.getenv('AML_SUBPATH', '') +SUBPATH = os.getenv("AML_SUBPATH", "") diff --git a/backend/experiment/forms.py b/backend/experiment/forms.py index 753917f7f..d1ab871ac 100644 --- a/backend/experiment/forms.py +++ b/backend/experiment/forms.py @@ -9,6 +9,7 @@ CheckboxSelectMultiple, TextInput, ) +from django_select2.forms import Select2MultipleWidget from experiment.models import ( Experiment, @@ -269,6 +270,9 @@ class Meta: "playlists", "theme_config", ] + widgets = { + "playlists": Select2MultipleWidget, # Use Select2 for the playlists field + } help_texts = { "image": "An image that will be displayed on the experiment page and as a meta image in search engines.", "slug": "The slug is used to identify the block in the URL so you can access it on the web as follows: app.amsterdammusiclab.nl/{slug}
\ diff --git a/backend/requirements.in/base.txt b/backend/requirements.in/base.txt index 1f32f7ee8..8e201c92b 100644 --- a/backend/requirements.in/base.txt +++ b/backend/requirements.in/base.txt @@ -45,3 +45,5 @@ mkdocs-material # Translate fields in Django models django-modeltranslation +# Django multiselect with search +django-select2 diff --git a/backend/requirements/dev.txt b/backend/requirements/dev.txt index 703a17405..ca8c02ed6 100644 --- a/backend/requirements/dev.txt +++ b/backend/requirements/dev.txt @@ -41,11 +41,15 @@ dill==0.3.8 django==4.2.16 # via # -r requirements.in/base.txt + # django-appconf # django-cors-headers # django-debug-toolbar # django-inline-actions # django-markup # django-modeltranslation + # django-select2 +django-appconf==1.0.6 + # via django-select2 django-cors-headers==3.10.0 # via -r requirements.in/base.txt django-debug-toolbar==4.3.0 @@ -54,9 +58,11 @@ django-inline-actions==2.4.0 # via -r requirements.in/base.txt django-markup[all-filter-dependencies,all_filter_dependencies]==1.8.1 # via -r requirements.in/base.txt -django-nested-admin==4.1.1 django-modeltranslation==0.19.9 # via -r requirements.in/base.txt +django-nested-admin==4.1.1 + # via -r requirements.in/base.txt +django-select2==8.2.1 # via -r requirements.in/base.txt docutils==0.20.1 # via diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index 254a6c598..7df28b18d 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -34,19 +34,25 @@ defusedxml==0.7.1 django==4.2.16 # via # -r requirements.in/base.txt + # django-appconf # django-cors-headers # django-inline-actions # django-markup # django-modeltranslation + # django-select2 +django-appconf==1.0.6 + # via django-select2 django-cors-headers==4.0.0 # via -r requirements.in/base.txt django-inline-actions==2.4.0 # via -r requirements.in/base.txt django-markup[all-filter-dependencies,all_filter_dependencies]==1.8.1 # via -r requirements.in/base.txt -django-nested-admin==4.1.1 django-modeltranslation==0.19.9 # via -r requirements.in/base.txt +django-nested-admin==4.1.1 + # via -r requirements.in/base.txt +django-select2==8.2.1 # via -r requirements.in/base.txt docutils==0.20.1 # via