Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/setup_evap/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ runs:
with:
arguments: "${{ inputs.shell }}"

- name: Add localsettings
run: cp evap/settings_test.py evap/localsettings.py
- run: |
echo "DJANGO_SETTINGS_MODULE=localsettings" >> "$GITHUB_ENV"
ln -s evap/development/cisettings.py localsettings.py
ln -s evap/settings/schema.pyi localsettings.pyi
shell: bash

- name: Create data/ directory
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
with:
submodules: true
- uses: DeterminateSystems/nix-installer-action@main
- run: echo "DJANGO_SETTINGS_MODULE=evap.development.cisettings" >> "$GITHUB_ENV"
- run: nix run .#build-dist
- run: tar tvf dist/*.tar.gz
- run: unzip -l dist/*.whl
Expand All @@ -92,6 +93,8 @@ jobs:
- run: pip install *.whl
- name: Check that "evaluation" section appears in help string
run: python -m evap --help | grep --fixed-strings "[evaluation]"
env:
DJANGO_SETTINGS_MODULE: evap.development.cisettings

test_frontend:
name: Test Frontend
Expand Down Expand Up @@ -225,28 +228,25 @@ jobs:
WHEEL=$(echo *.whl)
pip install $WHEEL[psycopg-binary]

- run: echo "DJANGO_SETTINGS_MODULE=evap.development.cisettings" >> "$GITHUB_ENV"
- name: Load test data
working-directory: deployment
run: |
cat <(echo 'from evap.settings import *') ../main/evap/settings_test.py | tee deployment_settings.py
python -m evap migrate
python -m evap loaddata test_data
env:
DJANGO_SETTINGS_MODULE: deployment_settings
- name: Backup database
working-directory: deployment
run: ./update_production.sh backup.json
env:
EVAP_OVERRIDE_BACKUP_FILENAME: true
EVAP_SKIP_UPDATE: true
EVAP_SKIP_APACHE_STEPS: true
DJANGO_SETTINGS_MODULE: deployment_settings
- name: Reload backup
working-directory: deployment
run: echo "yy" | ./load_production_backup.sh backup.json
env:
EVAP_SKIP_APACHE_STEPS: true
DJANGO_SETTINGS_MODULE: deployment_settings

macos-nix-build:
runs-on: macos-14
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ dist/
.DS_Store
thumbs.db

localsettings.py
localsettings.pyi
evap/localsettings.py
evap/static/css/evap.css
evap/static/css/evap.css.map
Expand Down
11 changes: 8 additions & 3 deletions evap/__main__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#!/usr/bin/env python3

import os
import logging
import sys

from django.conf import settings
from django.core.management import execute_from_command_line


def main():
assert not settings.configured
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evap.settings")
settings.DATADIR.mkdir(exist_ok=True)

if settings.TESTING:
from typeguard import install_import_hook # noqa: PLC0415

install_import_hook(("evap", "tools"))
logging.disable()

execute_from_command_line(sys.argv)


Expand Down
10 changes: 10 additions & 0 deletions evap/development/cisettings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from evap.settings import default, dev, local_services, open_id, test # noqa: TID251
from evap.settings_resolver import resolve_settings


class CiSettings:
DEBUG = True
SECRET_KEY = "evap-github-actions-secret-key" # nosec
Comment thread
richardebeling marked this conversation as resolved.


globals().update(resolve_settings([default, open_id, dev, local_services, test, CiSettings]))
59 changes: 6 additions & 53 deletions evap/development/localsettings.template.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,12 @@
# noqa: N999

from fractions import Fraction
from pathlib import Path
from evap.settings import debug_toolbar, default, dev, local_services, open_id, test # noqa: TID251
from evap.settings_resolver import resolve_settings

from django.utils.safestring import mark_safe

DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "evap",
"USER": "evap",
"PASSWORD": "evap",
# Absolute path to use unix domain socket
"HOST": Path("./data/").resolve(),
"CONN_MAX_AGE": 600,
}
}
class LocalSettings:
DEBUG = True
SECRET_KEY = "$SECRET_KEY" # nosec

REDIS_URL = f"unix://{Path('./data/redis.socket').resolve()}"

CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": f"{REDIS_URL}?db=0",
},
"results": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": f"{REDIS_URL}?db=1",
"TIMEOUT": None, # is always invalidated manually
},
"sessions": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": f"{REDIS_URL}?db=2",
},
}

# Make this unique, and don't share it with anybody.
SECRET_KEY = "$SECRET_KEY" # nosec

# Make apache work when DEBUG == False
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]

### Evaluation progress rewards
GLOBAL_EVALUATION_PROGRESS_REWARDS: list[tuple[Fraction, str]] = [
(Fraction("0"), "0€"),
(Fraction("0.25"), "1.000€"),
(Fraction("0.6"), "3.000€"),
(Fraction("0.7"), "7.000€"),
(Fraction("0.9"), "10.000€"),
]
GLOBAL_EVALUATION_PROGRESS_EXCLUDED_COURSE_TYPE_IDS: list[int] = []
GLOBAL_EVALUATION_PROGRESS_EXCLUDED_EVALUATION_IDS: list[int] = []
GLOBAL_EVALUATION_PROGRESS_INFO_TEXT = {
"de": mark_safe("Deine Teilnahme am Evaluationsprojekt wird helfen. Evaluiere also <b>jetzt</b>!"),
"en": mark_safe("Your participation in the evaluation helps, so evaluate <b>now</b>!"),
}
# Questionnaires automatically added to exam evaluations
EXAM_QUESTIONNAIRE_IDS = [111]
globals().update(resolve_settings([default, local_services, open_id, dev, test, debug_toolbar, LocalSettings]))
83 changes: 83 additions & 0 deletions evap/evaluation/tests/test_tools.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import graphlib
from argparse import Namespace
from unittest.mock import patch
from uuid import UUID

Expand All @@ -18,6 +20,7 @@
inside_transaction,
is_prefetched,
)
from evap.settings_resolver import derived, not_set, required, resolve_settings


class TestLanguageMiddleware(WebTest):
Expand Down Expand Up @@ -242,3 +245,83 @@ def test_inside_transaction(self):

with transaction.atomic():
self.assertTrue(inside_transaction())


class TestResolveSettings(TestCase):
def test_not_set(self):
# We don't use the "remove a previous setting" functionality anywhere, but this behavior is the natural composition of layers and not_set
self.assertEqual(resolve_settings([Namespace(FOO=42), Namespace(FOO=not_set())]), {})
self.assertEqual(resolve_settings([Namespace(FOO=not_set()), Namespace(FOO=42)]), {"FOO": 42})

def test_key_names(self):
# See SettingResolver.iter_settings
self.assertEqual(resolve_settings([Namespace(foo=1, _FOO=2, FoO=3)]), {})
Comment thread
niklasmohrin marked this conversation as resolved.

def test_derived(self):
self.assertEqual(
resolve_settings(
[
Namespace(
FOO=1,
BAR=derived(final={"FOO"})(lambda prev, final: final.FOO + 10),
),
Namespace(
FOO=derived(prev={"FOO"})(lambda prev, final: prev.FOO + 100),
BAR=derived(prev={"BAR"})(lambda prev, final: prev.BAR + 1000),
),
]
Comment thread
richardebeling marked this conversation as resolved.
),
{
"FOO": 101,
"BAR": 1111,
},
)

def test_cycle(self):
with self.assertRaises(graphlib.CycleError):
resolve_settings(
[
Namespace(
FOO=derived(final={"BAR"})(lambda prev, final: final.BAR),
BAR=derived(final={"FOO"})(lambda prev, final: final.FOO),
),
Namespace(
FOO=derived(prev={"FOO"})(lambda prev, final: prev.FOO),
BAR=derived(prev={"BAR"})(lambda prev, final: prev.BAR),
),
]
)

def test_required(self):
with self.assertRaises(ValueError):
resolve_settings([Namespace(FOO=required())])
with self.assertRaises(ValueError):
resolve_settings(
[Namespace(FOO=required()), Namespace(FOO=42, BAR=derived(prev={"FOO"})(lambda prev, final: prev.FOO))]
)
self.assertEqual(
resolve_settings(
[
Namespace(FOO=required()),
Namespace(FOO=42, BAR=derived(final={"FOO"})(lambda prev, final: final.FOO)),
]
),
{"FOO": 42, "BAR": 42},
)

def test_derived_final_dependencies(self):
# Ensure that derived final dependencies are ordered before in the topological order
self.assertEqual(
resolve_settings(
[
Namespace(FOO=required()),
Namespace(
FOO=42,
BAR=derived(final={"BAZ"})(lambda prev, final: final.BAZ),
BAZ=derived(final={"FOO"})(lambda prev, final: final.FOO),
),
Namespace(),
]
),
{"FOO": 42, "BAR": 42, "BAZ": 42},
)
Empty file added evap/settings/__init__.py
Empty file.
40 changes: 40 additions & 0 deletions evap/settings/debug_toolbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# pylint: disable=unused-argument,invalid-name

from evap.settings_resolver import derived

# Very helpful but eats a lot of performance on sql-heavy pages.
# Works only with DEBUG = True and Django's development server (so no apache).
ENABLE_DEBUG_TOOLBAR = False


@derived(final={"DEBUG", "ENABLE_DEBUG_TOOLBAR", "TESTING"})
def REALLY_ENABLE_DEBUG_TOOLBAR(prev, final):
return final.ENABLE_DEBUG_TOOLBAR and final.DEBUG and not final.TESTING


@derived(prev={"INSTALLED_APPS"}, final={"REALLY_ENABLE_DEBUG_TOOLBAR"})
def INSTALLED_APPS(prev, final):
if final.REALLY_ENABLE_DEBUG_TOOLBAR:
return prev.INSTALLED_APPS + ["debug_toolbar"]
return prev.INSTALLED_APPS


@derived(prev={"MIDDLEWARE"}, final={"REALLY_ENABLE_DEBUG_TOOLBAR"})
def MIDDLEWARE(prev, final):
if final.REALLY_ENABLE_DEBUG_TOOLBAR:
return ["debug_toolbar.middleware.DebugToolbarMiddleware"] + prev.MIDDLEWARE
return prev.MIDDLEWARE


def show_toolbar(request):
return True


@derived(prev={"DEBUG_TOOLBAR_CONFIG"}, final={"REALLY_ENABLE_DEBUG_TOOLBAR"})
def DEBUG_TOOLBAR_CONFIG(prev, final):
if final.REALLY_ENABLE_DEBUG_TOOLBAR:
return {
"SHOW_TOOLBAR_CALLBACK": "evap.settings.debug_toolbar.show_toolbar",
"JQUERY_URL": "",
}
return prev.DEBUG_TOOLBAR_CONFIG
Loading