Skip to content

Commit 3c00b63

Browse files
authored
Merge branch 'main' into shortcut-to-exam
2 parents 8ad5cf0 + e9856ca commit 3c00b63

File tree

91 files changed

+1716
-1072
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+1716
-1072
lines changed

.gitattributes

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Since text files will be used in a linux environment where LF is expected, git shouldn't change line endings to CRLF on windows machines
2+
* text=auto eol=lf
3+
4+
# things that fail without this:
5+
# * bash autocompletion (.sh): -bash: /etc/bash_completion.d/manage_autocompletion.sh: line 8: syntax error near unexpected token `$'\r''
6+
# * running python files: /usr/bin/env: ‘python3\r’: No such file or directory
7+
# Since that's a huge part of the code base, it doesn't really make sense to allow automatic EOL conversion for the rest of the files.

.gitmodules

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[submodule "evap/static/bootstrap"]
22
path = evap/static/bootstrap
33
url = https://github.com/twbs/bootstrap.git
4+
shallow = true

README.md

+28-14
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,37 @@ EvaP is the course evaluation system used internally at Hasso Plattner Institute
1212
For the documentation, please see our [wiki](https://github.com/e-valuation/EvaP/wiki).
1313

1414

15-
## Installation
15+
## Installation (for Development)
1616

1717
The easiest setup using [Vagrant](https://www.vagrantup.com) is shown here.
1818

19-
0. Install [git](https://git-scm.com/downloads), [Vagrant](https://www.vagrantup.com/downloads.html), and one of [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (recommended) or [Docker](https://docs.docker.com/engine/install/) (for ARM systems).
19+
1. Install [git](https://git-scm.com/downloads), [Vagrant](https://www.vagrantup.com/downloads.html), and one of [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (recommended) or [Docker](https://docs.docker.com/engine/install/) (for ARM systems).
2020

21-
1. Fork the EvaP repository (using the Fork-button in the upper right corner on GitHub).
21+
2. Run the following commands on the command line to clone the repository, create the Vagrant VM and run the Django development server.
22+
* If you are familiar with the fork-based open source workflow, create a fork and clone that (using SSH if you prefer that).
2223

23-
2. Windows users only (might not apply for the Linux subsystem):
24-
* Line endings: git's [`core.autocrlf` setting](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_core_autocrlf) has to be `false` or `input` so git does not convert line endings on checkout, because the code will be used in a Linux VM. We suggest using this command in Git Bash:
24+
* Windows users: We have observed [weird](https://www.github.com/git-for-windows/git/issues/4705) [behavior](https://www.github.com/git-for-windows/git/issues/4704) with SSH in Git Bash on Windows and thus recommend using PowerShell instead.
2525

26-
```bash
27-
git config --global core.autocrlf input
28-
```
26+
* To use Docker, replace `vagrant up` with `vagrant up --provider docker && vagrant provision`.
2927

30-
3. Run the following commands on the command line to clone the repository, create the Vagrant VM and run the Django development server.
31-
To use Docker, replace `vagrant up` with `vagrant up --provider docker && vagrant provision`.
3228
```bash
33-
git clone --recurse-submodules https://github.com/<your_github_username>/EvaP.git
29+
git clone --recurse-submodules https://github.com/e-valuation/EvaP.git
3430
cd EvaP
3531
vagrant up
3632
vagrant ssh
33+
```
34+
and, after the last command opened an SSH session in the development machine:
35+
```bash
3736
./manage.py run
3837
```
3938

40-
4. Open your browser at http://localhost:8000/ and login with email `evap@institution.example.com` and password `evap`.
41-
39+
3. Open your browser at http://localhost:8000/ and login with email `evap@institution.example.com` and password `evap`.
4240

4341
That's it!
4442

4543
## Contributing
4644

47-
We'd love to see contributions, feel free to fork! You should probably branch off `main`, the branch `release` is used for stable revisions.
45+
We'd love to see contributions! PRs solving existing issues are most helpful to us. It's best if you ask to be assigned for the issue so we won't have multiple people working on the same issue. Feel free to open issues for bugs, setup problems, or feature requests. If you have other questions, feel free to contact the [organization members](https://github.com/orgs/e-valuation/people). You should probably branch off `main`, the branch `release` is used for stable revisions.
4846

4947
Before committing, run the following commands:
5048
- `./manage.py test` (runs the test suite)
@@ -55,6 +53,22 @@ or, to combine all three, simply run `./manage.py precommit`.
5553

5654
You can also set up `pylint`, `isort`, `black` and `prettier` in your IDE to avoid doing this manually all the time.
5755

56+
### Creating a Pull Request (Workflow Suggestion)
57+
1. (once) [Fork](https://github.com/e-valuation/EvaP/fork) the repository so you have a GitHub repo that you have write access to.
58+
59+
2. (once) Set up some authentication for GitHub that allows push access. A common option is using [SSH keys](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh), the remaining instructions assume an SSH key setup. An alternative is using the [GitHub CLI tool](https://cli.github.com/).
60+
61+
3. (once) Ensure your [git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) are setup to use SSH. To fetch the up-to-date state of the official repo, it's useful to have an "upstream" remote configured:
62+
```bash
63+
git remote set-url origin git@github.com:<your-username>/EvaP.git
64+
git remote add upstream git@github.com:e-valuation/EvaP.git
65+
```
66+
67+
4. Create a branch (`git switch -c <your-branch-name>`), commit your changes (`git add` and `git commit`), and push them (`git push`). "Push" will ask you to specify an upstream branch (`git push -u origin <your-branch-name>`).
68+
69+
5. GitHub should now ask you whether you want to open a pull request ("PR"). If the PR solves an issue, use one of GitHub's [magic keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) (like "fixes") in the pull request description to create a link between your PR and the issue. If necessary, please also provide a short summary of your changes in the description.
70+
71+
5872
## License
5973

6074
MIT, see [LICENSE.md](LICENSE.md).

Vagrantfile

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ Vagrant.configure("2") do |config|
1818
end
1919
end
2020

21+
if Vagrant::Util::Platform.windows? then
22+
# workaround for git bash not automatically allocating a tty on windows in some scenarios
23+
# see https://github.com/hashicorp/vagrant/issues/9143#issuecomment-401088752
24+
config.ssh.extra_args = "-tt"
25+
end
26+
2127
config.vm.provider :docker do |d, override|
2228
d.image = "ubuntu:jammy"
2329
# Docker container really are supposed to be used differently. Hacky way to make it into a "VM".

evap/contributor/tests/test_views.py

+16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import xlrd
12
from django.core import mail
3+
from django.urls import reverse
24
from django_webtest import WebTest
35
from model_bakery import baker
46

@@ -284,3 +286,17 @@ def test_display_request_buttons(self):
284286
page = self.app.get(self.url, user=self.responsible)
285287
self.assertEqual(page.body.decode().count("Request changes"), 0)
286288
self.assertEqual(page.body.decode().count("Request creation of new account"), 2)
289+
290+
291+
class TestContributorResultsExportView(WebTest):
292+
@classmethod
293+
def setUpTestData(cls):
294+
result = create_evaluation_with_responsible_and_editor()
295+
cls.url = reverse("contributor:export")
296+
cls.user = result["responsible"]
297+
298+
def test_concise_header(self):
299+
response = self.app.get(self.url, user=self.user)
300+
301+
workbook = xlrd.open_workbook(file_contents=response.content)
302+
self.assertEqual(workbook.sheets()[0].row_values(0)[0], f"Evaluation\n{self.user.full_name}")

evap/contributor/views.py

+1
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ def export_contributor_results(contributor):
288288
include_not_enough_voters=True,
289289
include_unpublished=False,
290290
contributor=contributor,
291+
verbose_heading=False,
291292
)
292293
return response
293294

evap/development/fixtures/test_data.json

+76-4
Original file line numberDiff line numberDiff line change
@@ -132980,7 +132980,7 @@
132980132980
"title": "",
132981132981
"first_name_given": "",
132982132982
"first_name_chosen": "",
132983-
"last_name": "",
132983+
"last_name": "reviewer",
132984132984
"language": "",
132985132985
"is_proxy_user": false,
132986132986
"login_key": null,
@@ -133008,7 +133008,7 @@
133008133008
"title": "",
133009133009
"first_name_given": "",
133010133010
"first_name_chosen": "",
133011-
"last_name": "",
133011+
"last_name": "proxy",
133012133012
"language": "",
133013133013
"is_proxy_user": true,
133014133014
"login_key": null,
@@ -133043,7 +133043,7 @@
133043133043
"title": "",
133044133044
"first_name_given": "",
133045133045
"first_name_chosen": "",
133046-
"last_name": "",
133046+
"last_name": "proxy_delegate",
133047133047
"language": "",
133048133048
"is_proxy_user": false,
133049133049
"login_key": null,
@@ -133071,7 +133071,79 @@
133071133071
"title": "",
133072133072
"first_name_given": "",
133073133073
"first_name_chosen": "",
133074-
"last_name": "",
133074+
"last_name": "proxy_delegate_2",
133075+
"language": "",
133076+
"is_proxy_user": false,
133077+
"login_key": null,
133078+
"login_key_valid_until": null,
133079+
"is_active": true,
133080+
"notes": "",
133081+
"startpage": "DE",
133082+
"groups": [],
133083+
"user_permissions": [],
133084+
"delegates": [],
133085+
"cc_users": []
133086+
}
133087+
},
133088+
{
133089+
"model": "evaluation.userprofile",
133090+
"fields": {
133091+
"password": "eZAyFmtqHydCIFtGdbevAxiVjiRpqMtmaVUCrmkcfXdoJDigmGWPVNHeoYYyRojokKUJjsgPSPvZkjiiIHSIQlBfOKtQFDbZlPEyKnrQRrHdPtEhUYHqJauIlyIkYpBM",
133092+
"last_login": null,
133093+
"is_superuser": false,
133094+
"email": "vincenzo.boston@student.institution.example.com",
133095+
"title": "",
133096+
"first_name_given": "Vincenzo Alfredo",
133097+
"first_name_chosen": "",
133098+
"last_name": "Boston",
133099+
"language": "",
133100+
"is_proxy_user": false,
133101+
"login_key": null,
133102+
"login_key_valid_until": null,
133103+
"is_active": true,
133104+
"notes": "",
133105+
"startpage": "DE",
133106+
"groups": [],
133107+
"user_permissions": [],
133108+
"delegates": [],
133109+
"cc_users": []
133110+
}
133111+
},
133112+
{
133113+
"model": "evaluation.userprofile",
133114+
"fields": {
133115+
"password": "utAhMBbTpirVqtaoPpadEHdamaehnXWbEsliMMSnwDBYJcTnHluinAxkTeEupPoBzpuDBMYeXbpwmockMtQNYegbMuxkUBEBKqWGkOEFAWxzUFjdxevtIwYzvAgHCAwD",
133116+
"last_login": null,
133117+
"is_superuser": false,
133118+
"email": "bud.ledbetter@student.institution.example.com",
133119+
"title": "",
133120+
"first_name_given": "Bud",
133121+
"first_name_chosen": "",
133122+
"last_name": "LedBetter",
133123+
"language": "",
133124+
"is_proxy_user": false,
133125+
"login_key": null,
133126+
"login_key_valid_until": null,
133127+
"is_active": true,
133128+
"notes": "",
133129+
"startpage": "DE",
133130+
"groups": [],
133131+
"user_permissions": [],
133132+
"delegates": [],
133133+
"cc_users": []
133134+
}
133135+
},
133136+
{
133137+
"model": "evaluation.userprofile",
133138+
"fields": {
133139+
"password": "naFmzOVrFhXrVVLsIGFYceDAarTGwDRFZKGJwBvKhNFCpupezBrwhorUHsyQSpUxLFKSQuOurcIyoBBYRjARXjzcJCbqYRiKRMOwvdTqwNjAbYDhUKbopBPDYhANXUkI",
133140+
"last_login": null,
133141+
"is_superuser": false,
133142+
"email": "melody.large@student.institution.example.com",
133143+
"title": "",
133144+
"first_name_given": "Melody",
133145+
"first_name_chosen": "",
133146+
"last_name": "Large",
133075133147
"language": "",
133076133148
"is_proxy_user": false,
133077133149
"login_key": null,

evap/development/management/commands/dump_testdata.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import os
22

33
from django.conf import settings
4-
from django.core.management import call_command
54
from django.core.management.base import BaseCommand
65

6+
from evap.evaluation.management.commands.tools import logged_call_command
7+
78

89
class Command(BaseCommand):
910
args = ""
@@ -12,7 +13,8 @@ class Command(BaseCommand):
1213

1314
def handle(self, *args, **options):
1415
outfile_name = os.path.join(settings.BASE_DIR, "development", "fixtures", "test_data.json")
15-
call_command(
16+
logged_call_command(
17+
self.stdout,
1618
"dumpdata",
1719
"auth.group",
1820
"evaluation",
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from django.core.management import call_command
21
from django.core.management.base import BaseCommand
32

4-
from evap.evaluation.management.commands.tools import confirm_harmful_operation
3+
from evap.evaluation.management.commands.tools import confirm_harmful_operation, logged_call_command
54

65

76
class Command(BaseCommand):
@@ -14,24 +13,18 @@ def handle(self, *args, **options):
1413
if not confirm_harmful_operation(self.stdout):
1514
return
1615

17-
self.stdout.write('Executing "python manage.py reset_db"')
18-
call_command("reset_db", interactive=False)
16+
logged_call_command(self.stdout, "reset_db", interactive=False)
1917

20-
self.stdout.write('Executing "python manage.py migrate"')
21-
call_command("migrate")
18+
logged_call_command(self.stdout, "migrate")
2219

2320
# clear any data the migrations created.
2421
# their pks might differ from the ones in the dump, which results in errors on loaddata
25-
self.stdout.write('Executing "python manage.py flush"')
26-
call_command("flush", interactive=False)
22+
logged_call_command(self.stdout, "flush", interactive=False)
2723

28-
self.stdout.write('Executing "python manage.py loaddata test_data"')
29-
call_command("loaddata", "test_data")
24+
logged_call_command(self.stdout, "loaddata", "test_data")
3025

31-
self.stdout.write('Executing "python manage.py clear_cache --all -v=1"')
32-
call_command("clear_cache", "--all", "-v=1")
26+
logged_call_command(self.stdout, "clear_cache", "--all", "-v=1")
3327

34-
self.stdout.write('Executing "python manage.py refresh_results_cache"')
35-
call_command("refresh_results_cache")
28+
logged_call_command(self.stdout, "refresh_results_cache")
3629

3730
self.stdout.write("Done.")
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
from django.core.management import call_command
21
from django.core.management.base import BaseCommand
32

3+
from evap.evaluation.management.commands.tools import logged_call_command
4+
45

56
class Command(BaseCommand):
67
args = ""
78
help = 'Execute "makemessages --locale=de --ignore=node_modules/*"'
89

910
def handle(self, *args, **options):
10-
self.stdout.write('Executing "manage.py makemessages --locale=de --ignore=node_modules/*"')
11-
call_command("makemessages", "--locale=de", "--ignore=node_modules/*")
11+
logged_call_command(self.stdout, "makemessages", "--locale=de", "--ignore=node_modules/*")
12+
logged_call_command(
13+
self.stdout,
14+
"makemessages",
15+
"--domain=djangojs",
16+
"--extension=js,ts",
17+
"--locale=de",
18+
"--ignore=node_modules/*",
19+
"--ignore=evap/static/js/*.min.js",
20+
)

evap/development/tests/test_commands.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class TestDumpTestDataCommand(TestCase):
1111
@staticmethod
1212
def test_dumpdata_called():
13-
with patch("evap.development.management.commands.dump_testdata.call_command") as mock:
13+
with patch("evap.evaluation.management.commands.tools.call_command") as mock:
1414
management.call_command("dump_testdata")
1515

1616
outfile_name = os.path.join(settings.BASE_DIR, "development", "fixtures", "test_data.json")
@@ -31,7 +31,7 @@ def test_dumpdata_called():
3131

3232
class TestReloadTestdataCommand(TestCase):
3333
@patch("builtins.input")
34-
@patch("evap.development.management.commands.reload_testdata.call_command")
34+
@patch("evap.evaluation.management.commands.tools.call_command")
3535
def test_aborts(self, mock_call_command, mock_input):
3636
mock_input.return_value = "not yes"
3737

@@ -40,7 +40,7 @@ def test_aborts(self, mock_call_command, mock_input):
4040
self.assertEqual(mock_call_command.call_count, 0)
4141

4242
@patch("builtins.input")
43-
@patch("evap.development.management.commands.reload_testdata.call_command")
43+
@patch("evap.evaluation.management.commands.tools.call_command")
4444
def test_executes_key_commands(self, mock_call_command, mock_input):
4545
mock_input.return_value = "yes"
4646

evap/evaluation/management/commands/tools.py

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33

44
from django.conf import settings
5+
from django.core.management import call_command
56

67
logger = logging.getLogger(__name__)
78

@@ -40,3 +41,9 @@ def handle(self, *args, **options):
4041
raise
4142

4243
return NewClass
44+
45+
46+
def logged_call_command(stdout, *args, **kwargs):
47+
"""Log execution of management command with all args."""
48+
stdout.write("Executing python manage.py " + " ".join(list(args) + [f"{a}={b}" for a, b in kwargs.items()]))
49+
call_command(*args, **kwargs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.7 on 2023-11-13 20:59
2+
3+
from django.db import migrations
4+
import django_fsm
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("evaluation", "0141_userprofile_notes"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="evaluation",
15+
name="state",
16+
field=django_fsm.FSMIntegerField(default=10, protected=True, verbose_name="state"),
17+
),
18+
]

0 commit comments

Comments
 (0)