Skip to content

Commit 0cc8eca

Browse files
committed
Merge branch 'main' into iss2333
2 parents f28b071 + d5bf155 commit 0cc8eca

27 files changed

+246
-180
lines changed

.gitignore

-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ evap/localsettings.py
2626
evap/static/css/evap.css
2727
evap/static/css/evap.css.map
2828
evap/static/ts/rendered
29-
evap/static_collected
30-
evap/media
31-
evap/database.sqlite3
32-
evap/upload
3329

3430
evap/evaluation/templates/legal_notice_text.html
3531

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# EvaP - Evaluation Platform
22

3-
[![Build Status](https://github.com/e-valuation/EvaP/workflows/EvaP%20Test%20Suite/badge.svg?branch=main)](https://github.com/e-valuation/EvaP/actions?query=workflow%3A%22EvaP+Test+Suite%22)
3+
[![Build Status](https://github.com/e-valuation/EvaP/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/e-valuation/EvaP/actions?query=workflow%3A%22EvaP+Test+Suite%22)
44
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/2cf538781fdc4680a7103bcf96417a9a)](https://app.codacy.com/gh/e-valuation/EvaP/dashboard)
55
[![codecov](https://codecov.io/gh/e-valuation/EvaP/branch/main/graph/badge.svg)](https://codecov.io/gh/e-valuation/EvaP)
66

evap/development/fixtures/test_data.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -153476,7 +153476,8 @@
153476153476
"fields": {
153477153477
"name": "Big party",
153478153478
"date": "2099-12-31",
153479-
"redeem_end_date": "2099-12-30"
153479+
"redeem_end_date": "2099-12-30",
153480+
"step": 3
153480153481
}
153481153482
},
153482153483
{

evap/development/management/commands/dump_testdata.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
31
from django.conf import settings
42
from django.core.management.base import BaseCommand
53

@@ -12,7 +10,7 @@ class Command(BaseCommand):
1210
requires_migrations_checks = True
1311

1412
def handle(self, *args, **options):
15-
outfile_name = os.path.join(settings.BASE_DIR, "development", "fixtures", "test_data.json")
13+
outfile_name = settings.MODULE / "development" / "fixtures" / "test_data.json"
1614
logged_call_command(
1715
self.stdout,
1816
"dumpdata",

evap/development/management/commands/reload_testdata.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
import shutil
2+
3+
from django.conf import settings
14
from django.core.management.base import BaseCommand
25

36
from evap.evaluation.management.commands.tools import confirm_harmful_operation, logged_call_command
47

58

69
class Command(BaseCommand):
7-
args = ""
8-
help = "Drops the database, recreates it and then loads the testdata."
10+
help = "Drops the database, recreates it, and then loads the testdata. Also resets the upload directory."
11+
12+
def add_arguments(self, parser):
13+
parser.add_argument("--noinput", action="store_true")
914

1015
def handle(self, *args, **options):
1116
self.stdout.write("")
12-
self.stdout.write("WARNING! This will drop the database and cause IRREPARABLE DATA LOSS.")
13-
if not confirm_harmful_operation(self.stdout):
17+
self.stdout.write("WARNING! This will drop the database and upload directory and cause IRREPARABLE DATA LOSS.")
18+
if not options["noinput"] and not confirm_harmful_operation(self.stdout):
1419
return
1520

1621
logged_call_command(self.stdout, "reset_db", interactive=False)
@@ -27,4 +32,9 @@ def handle(self, *args, **options):
2732

2833
logged_call_command(self.stdout, "refresh_results_cache")
2934

35+
upload_dir = settings.MEDIA_ROOT
36+
if upload_dir.exists():
37+
shutil.rmtree(upload_dir)
38+
shutil.copytree(settings.MODULE / "development" / "fixtures" / "upload", upload_dir)
39+
3040
self.stdout.write("Done.")

evap/development/tests/test_commands.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from io import StringIO
32
from unittest.mock import call, patch
43

@@ -14,7 +13,7 @@ def test_dumpdata_called():
1413
with patch("evap.evaluation.management.commands.tools.call_command") as mock:
1514
management.call_command("dump_testdata", stdout=StringIO())
1615

17-
outfile_name = os.path.join(settings.BASE_DIR, "development", "fixtures", "test_data.json")
16+
outfile_name = settings.MODULE / "development" / "fixtures" / "test_data.json"
1817
mock.assert_called_once_with(
1918
"dumpdata",
2019
"auth.group",
@@ -31,20 +30,20 @@ def test_dumpdata_called():
3130

3231

3332
class TestReloadTestdataCommand(TestCase):
34-
@patch("builtins.input")
33+
@patch("builtins.input", return_value="not yes")
34+
@patch("evap.development.management.commands.reload_testdata.shutil")
3535
@patch("evap.evaluation.management.commands.tools.call_command")
36-
def test_aborts(self, mock_call_command, mock_input):
37-
mock_input.return_value = "not yes"
38-
36+
def test_aborts(self, mock_call_command, mock_shutil, _mock_input):
3937
management.call_command("reload_testdata", stdout=StringIO())
4038

4139
self.assertEqual(mock_call_command.call_count, 0)
40+
self.assertFalse(mock_shutil.method_calls)
4241

43-
@patch("builtins.input")
42+
@patch("builtins.input", return_value="yes")
43+
@patch("pathlib.Path.exists", return_value=True)
44+
@patch("evap.development.management.commands.reload_testdata.shutil")
4445
@patch("evap.evaluation.management.commands.tools.call_command")
45-
def test_executes_key_commands(self, mock_call_command, mock_input):
46-
mock_input.return_value = "yes"
47-
46+
def test_executes_key_commands(self, mock_call_command, mock_shutil, mock_exists, _mock_input):
4847
management.call_command("reload_testdata", stdout=StringIO())
4948

5049
mock_call_command.assert_any_call("reset_db", interactive=False)
@@ -56,6 +55,11 @@ def test_executes_key_commands(self, mock_call_command, mock_input):
5655

5756
self.assertEqual(mock_call_command.call_count, 6)
5857

58+
# The directory for uploads is cleared and reinitialized
59+
mock_exists.assert_called_once()
60+
mock_shutil.rmtree.assert_called_once()
61+
mock_shutil.copytree.assert_called_once()
62+
5963

6064
class TestRunCommand(TestCase):
6165
def test_calls_runserver(self):

evap/development/views.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
31
from django.conf import settings
42
from django.core.exceptions import BadRequest
53
from django.http import HttpResponse
@@ -16,9 +14,9 @@ def development_components(request):
1614

1715

1816
def development_rendered(request, filename):
19-
fixtures_directory = os.path.join(settings.STATICFILES_DIRS[0], "ts", "rendered")
17+
fixtures_directory = settings.STATICFILES_DIRS[0] / "ts" / "rendered"
2018
try:
21-
with open(os.path.join(fixtures_directory, filename), encoding="utf-8") as fixture:
19+
with open(fixtures_directory / filename, encoding="utf-8") as fixture:
2220
return HttpResponse(fixture)
2321
except (FileNotFoundError, ValueError, OSError) as e:
2422
raise BadRequest from e

evap/evaluation/management/commands/scss.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import subprocess # nosec
32

43
from django.conf import settings
@@ -24,8 +23,8 @@ def handle(self, *args, **options):
2423
command = [
2524
"npx",
2625
"sass",
27-
os.path.join(static_directory, "scss", "evap.scss"),
28-
os.path.join(static_directory, "css", "evap.css"),
26+
static_directory / "scss" / "evap.scss",
27+
static_directory / "css" / "evap.css",
2928
]
3029

3130
if options["watch"]:

evap/evaluation/management/commands/ts.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import argparse
2-
import os
32
import subprocess # nosec
43
import unittest
54

@@ -67,17 +66,14 @@ def compile(self, watch=False, fresh=False, **_options):
6766
"npx",
6867
"tsc",
6968
"--project",
70-
os.path.join(static_directory, "ts", "tsconfig.compile.json"),
69+
static_directory / "ts" / "tsconfig.compile.json",
7170
]
7271

7372
if watch:
7473
command += ["--watch"]
7574

7675
if fresh:
77-
try:
78-
os.remove(os.path.join(static_directory, "ts", ".tsbuildinfo.json"))
79-
except FileNotFoundError:
80-
pass
76+
(static_directory / "ts" / ".tsbuildinfo.json").unlink(missing_ok=True)
8177

8278
self.run_command(command)
8379

evap/evaluation/tests/test_commands.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import random
32
from collections import defaultdict
43
from datetime import date, datetime, timedelta
@@ -204,8 +203,8 @@ def test_calls_cache_results(self):
204203

205204
class TestScssCommand(TestCase):
206205
def setUp(self):
207-
self.scss_path = os.path.join(settings.STATICFILES_DIRS[0], "scss", "evap.scss")
208-
self.css_path = os.path.join(settings.STATICFILES_DIRS[0], "css", "evap.css")
206+
self.scss_path = settings.STATICFILES_DIRS[0] / "scss" / "evap.scss"
207+
self.css_path = settings.STATICFILES_DIRS[0] / "css" / "evap.css"
209208

210209
@patch("subprocess.run")
211210
def test_scss_called(self, mock_subprocess_run):
@@ -246,14 +245,14 @@ def test_scss_called_with_no_sass_installed(self, mock_subprocess_run):
246245

247246
class TestTsCommand(TestCase):
248247
def setUp(self):
249-
self.ts_path = os.path.join(settings.STATICFILES_DIRS[0], "ts")
248+
self.ts_path = settings.STATICFILES_DIRS[0] / "ts"
250249

251250
@patch("subprocess.run")
252251
def test_ts_compile(self, mock_subprocess_run):
253252
management.call_command("ts", "compile")
254253

255254
mock_subprocess_run.assert_called_once_with(
256-
["npx", "tsc", "--project", os.path.join(self.ts_path, "tsconfig.compile.json")],
255+
["npx", "tsc", "--project", self.ts_path / "tsconfig.compile.json"],
257256
check=True,
258257
)
259258

@@ -264,7 +263,7 @@ def test_ts_compile_with_watch(self, mock_subprocess_run):
264263
management.call_command("ts", "compile", "--watch")
265264

266265
mock_subprocess_run.assert_called_once_with(
267-
["npx", "tsc", "--project", os.path.join(self.ts_path, "tsconfig.compile.json"), "--watch"],
266+
["npx", "tsc", "--project", self.ts_path / "tsconfig.compile.json", "--watch"],
268267
check=True,
269268
)
270269

@@ -280,7 +279,7 @@ def test_ts_test(self, mock_render_pages, mock_call_command, mock_subprocess_run
280279
mock_subprocess_run.assert_has_calls(
281280
[
282281
call(
283-
["npx", "tsc", "--project", os.path.join(self.ts_path, "tsconfig.compile.json")],
282+
["npx", "tsc", "--project", self.ts_path / "tsconfig.compile.json"],
284283
check=True,
285284
),
286285
call(["npx", "jest"], check=True),

evap/evaluation/tests/test_misc.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os.path
21
from io import StringIO
32

43
from django.conf import settings
@@ -29,7 +28,7 @@ def test_sample_semester_file(self):
2928
original_user_count = UserProfile.objects.count()
3029

3130
form = page.forms["semester-import-form"]
32-
form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample.xlsx"),)
31+
form["excel_file"] = (str(settings.MODULE / "static" / "sample.xlsx"),)
3332
page = form.submit(name="operation", value="test")
3433

3534
form = page.forms["semester-import-form"]
@@ -45,7 +44,7 @@ def test_sample_user_file(self):
4544
original_user_count = UserProfile.objects.count()
4645

4746
form = page.forms["user-import-form"]
48-
form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample_user.xlsx"),)
47+
form["excel_file"] = (str(settings.MODULE / "static" / "sample_user.xlsx"),)
4948
page = form.submit(name="operation", value="test")
5049

5150
form = page.forms["user-import-form"]

evap/evaluation/tests/tools.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ def let_user_vote_for_evaluation(user, evaluation, create_answers=False):
103103

104104

105105
def store_ts_test_asset(relative_path: str, content) -> None:
106-
absolute_path = os.path.join(settings.STATICFILES_DIRS[0], "ts", "rendered", relative_path)
106+
absolute_path = settings.STATICFILES_DIRS[0] / "ts" / "rendered" / relative_path
107107

108-
os.makedirs(os.path.dirname(absolute_path), exist_ok=True)
108+
absolute_path.parent.mkdir(parents=True, exist_ok=True)
109109

110110
with open(absolute_path, "wb") as file:
111111
file.write(content)

evap/logs/.gitignore

-4
This file was deleted.

evap/rewards/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class RewardPointRedemptionEvent(models.Model):
1111
name = models.CharField(max_length=1024, verbose_name=_("event name"))
1212
date = models.DateField(verbose_name=_("event date"))
1313
redeem_end_date = models.DateField(verbose_name=_("redemption end date"))
14+
# Note that we allow this value to change throughout the lifetime of the event.
1415
step = models.PositiveSmallIntegerField(
1516
verbose_name=_("redemption step"), help_text=_("Only multiples of this step can be redeemed."), default=1
1617
)

evap/settings.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@
99
"""
1010

1111
import logging
12-
import os
1312
import sys
1413
from fractions import Fraction
14+
from pathlib import Path
1515
from typing import Any
1616

1717
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
1818

1919
from evap.tools import MonthAndDay
2020

21-
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
22-
21+
MODULE = Path(__file__).parent.resolve()
22+
CWD = Path(".").resolve()
23+
DATADIR = CWD / "data"
2324

2425
### Debugging
2526

@@ -184,7 +185,7 @@ class ManifestStaticFilesStorageWithJsReplacement(ManifestStaticFilesStorage):
184185
"file": {
185186
"level": "DEBUG",
186187
"class": "logging.handlers.RotatingFileHandler",
187-
"filename": BASE_DIR + "/logs/evap.log",
188+
"filename": DATADIR / "evap.log",
188189
"maxBytes": 1024 * 1024 * 10,
189190
"backupCount": 5,
190191
"formatter": "default",
@@ -334,7 +335,7 @@ class ManifestStaticFilesStorageWithJsReplacement(ManifestStaticFilesStorage):
334335

335336
USE_TZ = False
336337

337-
LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
338+
LOCALE_PATHS = [MODULE / "locale"]
338339

339340
FORMAT_MODULE_PATH = ["evap.locale"]
340341

@@ -351,17 +352,17 @@ class ManifestStaticFilesStorageWithJsReplacement(ManifestStaticFilesStorage):
351352

352353
# Additional locations of static files
353354
STATICFILES_DIRS = [
354-
os.path.join(BASE_DIR, "static"),
355+
MODULE / "static",
355356
]
356357

357358
# Absolute path to the directory static files should be collected to.
358-
STATIC_ROOT = os.path.join(BASE_DIR, "static_collected")
359+
STATIC_ROOT = DATADIR / "static_collected"
359360

360361

361362
### User-uploaded files
362363

363364
# Absolute filesystem path to the directory that will hold user-uploaded files.
364-
MEDIA_ROOT = os.path.join(BASE_DIR, "upload")
365+
MEDIA_ROOT = DATADIR / "upload"
365366

366367
### Evaluation progress rewards
367368
GLOBAL_EVALUATION_PROGRESS_REWARDS: list[tuple[Fraction, str]] = (

evap/staff/staff_mode.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def middleware(request):
3030
messages.info(request, _("Your staff mode timed out."))
3131

3232
if is_in_staff_mode(request):
33+
assert request.user.has_staff_permission
3334
request.user.is_participant = False
3435
request.user.is_student = False
3536
request.user.is_editor = False
@@ -52,7 +53,9 @@ def is_in_staff_mode(request):
5253

5354

5455
def update_staff_mode(request):
55-
assert request.user.has_staff_permission
56+
if not request.user.has_staff_permission:
57+
exit_staff_mode(request)
58+
return
5659

5760
request.session["staff_mode_start_time"] = time.time()
5861
request.session.modified = True

0 commit comments

Comments
 (0)