ladok3 4.9__py3-none-any.whl → 4.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doc/ltxobj/ladok3.pdf +0 -0
- ladok3/__init__.py +5009 -4668
- ladok3/cli.nw +5 -1
- ladok3/cli.py +5 -1
- ladok3/data.py +180 -142
- ladok3/ladok3.nw +118 -59
- ladok3/report.nw +1 -1
- ladok3/report.py +142 -105
- {ladok3-4.9.dist-info → ladok3-4.10.dist-info}/METADATA +22 -4
- ladok3-4.10.dist-info/RECORD +21 -0
- ladok3/kth.py +0 -117
- ladok3/test.py +0 -0
- ladok3-4.9.dist-info/RECORD +0 -23
- {ladok3-4.9.dist-info → ladok3-4.10.dist-info}/LICENSE +0 -0
- {ladok3-4.9.dist-info → ladok3-4.10.dist-info}/WHEEL +0 -0
- {ladok3-4.9.dist-info → ladok3-4.10.dist-info}/entry_points.txt +0 -0
ladok3/cli.nw
CHANGED
|
@@ -504,7 +504,11 @@ If all fail, the function will return [[None]] for both.
|
|
|
504
504
|
(This is due to how we handle the [[login]] command.)
|
|
505
505
|
<<functions>>=
|
|
506
506
|
def load_credentials(filename="config.json"):
|
|
507
|
-
"""
|
|
507
|
+
"""
|
|
508
|
+
Loads credentials from environment or file named filename.
|
|
509
|
+
Returns the tuple (instituation, credential dictionary) that
|
|
510
|
+
can be passed to `LadokSession(instiution, credential dictionary)`.
|
|
511
|
+
"""
|
|
508
512
|
|
|
509
513
|
<<fetch vars from keyring>>
|
|
510
514
|
<<fetch username and password from keyring>>
|
ladok3/cli.py
CHANGED
|
@@ -201,7 +201,11 @@ Note: Your password will be visible on screen during this process.
|
|
|
201
201
|
if new_val:
|
|
202
202
|
vars[key] = new_val
|
|
203
203
|
def load_credentials(filename="config.json"):
|
|
204
|
-
"""
|
|
204
|
+
"""
|
|
205
|
+
Loads credentials from environment or file named filename.
|
|
206
|
+
Returns the tuple (instituation, credential dictionary) that
|
|
207
|
+
can be passed to `LadokSession(instiution, credential dictionary)`.
|
|
208
|
+
"""
|
|
205
209
|
|
|
206
210
|
try:
|
|
207
211
|
institution = keyring.get_password("ladok3", "institution")
|
ladok3/data.py
CHANGED
|
@@ -4,164 +4,202 @@ import ladok3
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
def filter_rounds(all_rounds, desired_rounds):
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
9
|
+
"""Returns only the round objects with round code in desired_rounds."""
|
|
10
|
+
if not desired_rounds:
|
|
11
|
+
return all_rounds
|
|
12
|
+
return filter(lambda x: x.round_code in desired_rounds, all_rounds)
|
|
13
|
+
|
|
14
|
+
|
|
15
15
|
def extract_data_for_round(ladok, course_round, args):
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
16
|
+
course_start = course_round.start
|
|
17
|
+
course_length = course_round.end - course_start
|
|
18
|
+
component = course_round.components()[0]
|
|
19
|
+
results = ladok.search_reported_results_JSON(
|
|
20
|
+
course_round.round_id, component.instance_id
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
students = filter_students(course_round.participants(), args.students)
|
|
24
|
+
|
|
25
|
+
for student in students:
|
|
26
|
+
student_results = filter_student_results(student, results)
|
|
27
|
+
|
|
28
|
+
if not should_include(ladok, student, course_round, student_results):
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
components = filter_components(course_round.components(), args.components)
|
|
32
|
+
|
|
33
|
+
for component in components:
|
|
34
|
+
if len(student_results) < 1:
|
|
35
|
+
result_data = None
|
|
36
|
+
else:
|
|
37
|
+
result_data = filter_component_result(
|
|
38
|
+
component, student_results[0]["ResultatPaUtbildningar"]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if not result_data:
|
|
42
|
+
grade = "-"
|
|
43
|
+
normalized_date = None
|
|
44
|
+
else:
|
|
45
|
+
if "Betygsgradsobjekt" in result_data:
|
|
46
|
+
grade = result_data["Betygsgradsobjekt"]["Kod"]
|
|
47
|
+
try:
|
|
48
|
+
date = datetime.date.fromisoformat(
|
|
49
|
+
result_data["Examinationsdatum"]
|
|
50
|
+
)
|
|
51
|
+
except KeyError:
|
|
52
|
+
normalized_date = None
|
|
53
|
+
grade = "-"
|
|
54
|
+
else:
|
|
55
|
+
normalized_date = (date - course_start) / course_length
|
|
56
|
+
if args.time_limit and normalized_date > args.time_limit:
|
|
57
|
+
grade = "-"
|
|
58
|
+
normalized_date = None
|
|
59
|
+
else:
|
|
60
|
+
grade = "-"
|
|
61
|
+
normalized_date = None
|
|
62
|
+
|
|
63
|
+
yield student, component, grade, normalized_date
|
|
64
|
+
|
|
59
65
|
|
|
60
|
-
yield student, component, grade, normalized_date
|
|
61
66
|
def filter_student_results(student, results):
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
return list(filter(lambda x: x["Student"]["Uid"] == student.ladok_id, results))
|
|
68
|
+
|
|
69
|
+
|
|
65
70
|
def filter_component_result(component, results):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
for component_result in results:
|
|
72
|
+
if "Arbetsunderlag" in component_result:
|
|
73
|
+
result_data = component_result["Arbetsunderlag"]
|
|
74
|
+
elif "SenastAttesteradeResultat" in component_result:
|
|
75
|
+
result_data = component_result["SenastAttesteradeResultat"]
|
|
76
|
+
else:
|
|
77
|
+
continue
|
|
78
|
+
if component.instance_id != result_data["UtbildningsinstansUID"]:
|
|
79
|
+
continue
|
|
80
|
+
return result_data
|
|
81
|
+
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
78
85
|
def filter_students(all_students, desired_students):
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
all_students
|
|
85
|
-
)
|
|
86
|
+
"""Returns only the students with personnummer in desired_students."""
|
|
87
|
+
if not desired_students:
|
|
88
|
+
return all_students
|
|
89
|
+
return filter(lambda x: x.personnummer in desired_students, all_students)
|
|
90
|
+
|
|
86
91
|
|
|
87
92
|
def filter_components(all_components, desired_components):
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
93
|
+
"""Returns only the components with a code in the desired_components."""
|
|
94
|
+
if not desired_components:
|
|
95
|
+
return all_components
|
|
96
|
+
return filter(lambda x: x.code in desired_components, all_components)
|
|
97
|
+
|
|
98
|
+
|
|
95
99
|
def should_include(ladok, student, course_round, result):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
"""Returns True if student should be included, False if to be excluded"""
|
|
101
|
+
if is_reregistered(ladok, student.ladok_id, course_round):
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
if has_credit_transfer(result):
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
return True
|
|
99
108
|
|
|
100
|
-
if has_credit_transfer(result):
|
|
101
|
-
return False
|
|
102
109
|
|
|
103
|
-
return True
|
|
104
110
|
def is_reregistered(ladok, student_id, course):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
"""Check if the student is reregistered on the course round course."""
|
|
112
|
+
registrations = ladok.registrations_on_course_JSON(course.education_id, student_id)
|
|
113
|
+
registrations.sort(
|
|
114
|
+
key=lambda x: x["Utbildningsinformation"]["Studieperiod"]["Startdatum"]
|
|
115
|
+
)
|
|
116
|
+
first_reg = registrations[0]
|
|
117
|
+
return (
|
|
118
|
+
first_reg["Utbildningsinformation"]["Utbildningstillfalleskod"]
|
|
119
|
+
!= course.round_code
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
113
123
|
def has_credit_transfer(results):
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
"""Returns True if there exists a credit tranfer among the results."""
|
|
125
|
+
for result in results:
|
|
126
|
+
for component_result in result["ResultatPaUtbildningar"]:
|
|
127
|
+
if component_result["HarTillgodoraknande"]:
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
return False
|
|
119
131
|
|
|
120
|
-
return False
|
|
121
132
|
|
|
122
133
|
def add_command_options(parser):
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
data_parser = parser.add_parser(
|
|
135
|
+
"data",
|
|
136
|
+
help="Returns course results data in CSV form",
|
|
137
|
+
description="""
|
|
126
138
|
Returns the results in CSV form for all first-time registered students.
|
|
127
|
-
""".strip()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
""".strip(),
|
|
140
|
+
)
|
|
141
|
+
data_parser.set_defaults(func=command)
|
|
142
|
+
data_parser.add_argument(
|
|
143
|
+
"course_code", help="The course code of the course for which to export data"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
data_parser.add_argument(
|
|
147
|
+
"-d",
|
|
148
|
+
"--delimiter",
|
|
149
|
+
default="\t",
|
|
150
|
+
help="The delimiter for the CSV output; "
|
|
151
|
+
"default is a tab character to be compatible with POSIX commands, "
|
|
152
|
+
"use `-d,` or `-d ,` to get comma-separated values.",
|
|
153
|
+
)
|
|
154
|
+
data_parser.add_argument(
|
|
155
|
+
"-r",
|
|
156
|
+
"--rounds",
|
|
157
|
+
nargs="+",
|
|
158
|
+
help="The round codes for the rounds to include, "
|
|
159
|
+
"otherwise all rounds will be included.",
|
|
160
|
+
)
|
|
161
|
+
data_parser.add_argument(
|
|
162
|
+
"-t",
|
|
163
|
+
"--time-limit",
|
|
164
|
+
type=float,
|
|
165
|
+
help="The time (normalized) for cutting off results, "
|
|
166
|
+
"use `-t 1.0` to cut off at course end.",
|
|
167
|
+
)
|
|
168
|
+
data_parser.add_argument(
|
|
169
|
+
"-s",
|
|
170
|
+
"--students",
|
|
171
|
+
nargs="+",
|
|
172
|
+
help="List of personnummer for students to include, "
|
|
173
|
+
"otherwise all students will be included.",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
data_parser.add_argument(
|
|
177
|
+
"-c",
|
|
178
|
+
"--components",
|
|
179
|
+
nargs="+",
|
|
180
|
+
help="List of component codes for components to include, "
|
|
181
|
+
"otherwise all components will be included.",
|
|
182
|
+
)
|
|
183
|
+
|
|
150
184
|
|
|
151
185
|
def command(ladok, args):
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
data_writer = csv.writer(sys.stdout, delimiter=args.delimiter)
|
|
187
|
+
course_rounds = filter_rounds(
|
|
188
|
+
ladok.search_course_rounds(code=args.course_code), args.rounds
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
data_writer.writerow(["Course", "Round", "Component", "Student", "Grade", "Time"])
|
|
192
|
+
for course_round in course_rounds:
|
|
193
|
+
data = extract_data_for_round(ladok, course_round, args)
|
|
194
|
+
|
|
195
|
+
for student, component, grade, time in data:
|
|
196
|
+
data_writer.writerow(
|
|
197
|
+
[
|
|
198
|
+
course_round.code,
|
|
199
|
+
course_round.round_code,
|
|
200
|
+
component,
|
|
201
|
+
student,
|
|
202
|
+
grade,
|
|
203
|
+
time,
|
|
204
|
+
]
|
|
205
|
+
)
|
ladok3/ladok3.nw
CHANGED
|
@@ -46,6 +46,17 @@ It uses the [[requests]] module.
|
|
|
46
46
|
We can use the class as follows.
|
|
47
47
|
\inputminted{python}{../examples/example_LadokSession.py}
|
|
48
48
|
|
|
49
|
+
An alternative way would be to use the general authentication procedure that
|
|
50
|
+
seems to work at most Swedish universities.
|
|
51
|
+
\begin{minted}[linenos]{python}
|
|
52
|
+
import ladok3
|
|
53
|
+
|
|
54
|
+
ladok = ladok3.LadokSession("KTH Royal Institute of Technology",
|
|
55
|
+
vars={"username": os.environ["KTH_LOGIN"],
|
|
56
|
+
"password": os.environ["KTH_PASSWD"]},
|
|
57
|
+
test_environment=True) # for experiments
|
|
58
|
+
\end{minted}
|
|
59
|
+
|
|
49
60
|
This chapter covers how the [[LadokSession]] class work.
|
|
50
61
|
The remaining chapters cover what the [[ladok]] object can be used for.
|
|
51
62
|
\Cref{StudentClasses} covers how we can work with student data.
|
|
@@ -1034,27 +1045,126 @@ This is essentially an \enquote{instance of a course syllabus}.
|
|
|
1034
1045
|
We know from above that it inherits from [[LadokRemoteData]] and that it must
|
|
1035
1046
|
be initialized with the keywords [[ladok]] (for its parent) and
|
|
1036
1047
|
[[UtbildningsinstansUID]] (for itself) and optionally some data.
|
|
1037
|
-
This leaves the following methods.
|
|
1038
1048
|
<<CourseInstance methods>>=
|
|
1039
1049
|
def __init__(self, /, **kwargs):
|
|
1040
1050
|
self.__instance_id = kwargs.pop("UtbildningsinstansUID")
|
|
1041
|
-
super().__init__(**kwargs)
|
|
1051
|
+
super().__init__(**kwargs) # sets self.ladok
|
|
1042
1052
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1053
|
+
<<CourseInstance constructor body>>
|
|
1054
|
+
@
|
|
1055
|
+
|
|
1056
|
+
We can fetch the data from LADOK as follows.
|
|
1057
|
+
Note that we need to use the [[round_id]] from the [[CourseRound]] object.
|
|
1058
|
+
This works since we never have a [[CourseInstance]] object on its own, it's
|
|
1059
|
+
always parent of a [[CourseRound]] object.
|
|
1060
|
+
(And that makes this kind of ugly from an OOP perspective.)
|
|
1061
|
+
<<fetch CourseInstance data from LADOK>>=
|
|
1062
|
+
data = self.ladok.course_round_components_JSON(self.round_id)
|
|
1063
|
+
@
|
|
1064
|
+
|
|
1065
|
+
Then data will be populated with the following values:
|
|
1066
|
+
\begin{pycode}
|
|
1067
|
+
import json
|
|
1068
|
+
import ladok3
|
|
1069
|
+
import os
|
|
1047
1070
|
|
|
1071
|
+
ladok = ladok3.LadokSession(
|
|
1072
|
+
os.environ["LADOK_INST"],
|
|
1073
|
+
vars={"username": os.environ["LADOK_USER"],
|
|
1074
|
+
"password": os.environ["LADOK_PASS"]},
|
|
1075
|
+
test_environment=True)
|
|
1076
|
+
|
|
1077
|
+
print(r"\begin{minted}{JSON}")
|
|
1078
|
+
data = ladok.course_round_components_JSON(
|
|
1079
|
+
"cf7045a7-3e1c-11eb-b960-5f936a674375")
|
|
1080
|
+
ladok3.clean_data(data)
|
|
1081
|
+
print(json.dumps(data, indent=2))
|
|
1082
|
+
print(r"\end{minted}")
|
|
1083
|
+
\end{pycode}
|
|
1084
|
+
|
|
1085
|
+
Now that we have all data in [[data]], we can assign its values to the private
|
|
1086
|
+
attributes.
|
|
1087
|
+
We note, however, that some course instances lack both [[Versionsnummer]] and
|
|
1088
|
+
[[Omfattning]].
|
|
1089
|
+
It seems like faux courses are created to document when students go on
|
|
1090
|
+
Exchanges.
|
|
1091
|
+
These faux courses have a name and code, but no credits or version of syllabus.
|
|
1092
|
+
Consequently they don't have any grading scales either.
|
|
1093
|
+
|
|
1094
|
+
Our approach will be to try to assign the attributes using [[kwargs]], and if
|
|
1095
|
+
that fails, we will fetch the data (pull from LADOK).
|
|
1096
|
+
If that pull fails with missing data, then we will assume that it's one of
|
|
1097
|
+
these faux courses.
|
|
1098
|
+
<<CourseInstance constructor body>>=
|
|
1099
|
+
try:
|
|
1100
|
+
self.__assign_attr(kwargs)
|
|
1101
|
+
except:
|
|
1102
|
+
self.__pull_attributes()
|
|
1103
|
+
<<CourseInstance methods>>=
|
|
1048
1104
|
def __assign_attr(self, data):
|
|
1049
1105
|
<<assign CourseInstance data to private attributes>>
|
|
1050
1106
|
|
|
1051
1107
|
def __pull_attributes(self):
|
|
1052
|
-
<<fetch CourseInstance data
|
|
1053
|
-
|
|
1108
|
+
<<fetch CourseInstance data from LADOK>>
|
|
1109
|
+
try:
|
|
1110
|
+
self.__assign_attr(data)
|
|
1111
|
+
except:
|
|
1112
|
+
self.__assign_faux(data)
|
|
1054
1113
|
|
|
1055
1114
|
def pull(self):
|
|
1056
1115
|
self.__pull_attributes()
|
|
1057
1116
|
|
|
1117
|
+
def __assign_faux(self, data):
|
|
1118
|
+
<<assign faux CourseInstance data to private attributes>>
|
|
1119
|
+
@
|
|
1120
|
+
|
|
1121
|
+
By trial-and-error, it seems like the faux courses has none of the attributes
|
|
1122
|
+
that the real courses have.
|
|
1123
|
+
However, we try our best.
|
|
1124
|
+
<<assign common CourseInstance data to private attributes>>=
|
|
1125
|
+
if "IngaendeMoment" in data:
|
|
1126
|
+
self.__components = [CourseComponent(
|
|
1127
|
+
ladok=self.ladok, course=self,
|
|
1128
|
+
**component) for component in data["IngaendeMoment"]]
|
|
1129
|
+
else:
|
|
1130
|
+
self.__components = []
|
|
1131
|
+
<<assign CourseInstance data to private attributes>>=
|
|
1132
|
+
<<assign common CourseInstance data to private attributes>>
|
|
1133
|
+
|
|
1134
|
+
self.__name = data.pop("Benamning")
|
|
1135
|
+
self.__code = data.pop("Utbildningskod")
|
|
1136
|
+
|
|
1137
|
+
self.__credits = data.pop("Omfattning")
|
|
1138
|
+
self.__unit = data.pop("Enhet")
|
|
1139
|
+
|
|
1140
|
+
self.__version = data.pop("Versionsnummer")
|
|
1141
|
+
|
|
1142
|
+
self.__education_id = data.pop("UtbildningUID")
|
|
1143
|
+
|
|
1144
|
+
self.__grade_scale = self.ladok.get_grade_scales(
|
|
1145
|
+
id=data.pop("BetygsskalaID"))
|
|
1146
|
+
<<assign faux CourseInstance data to private attributes>>=
|
|
1147
|
+
<<assign common CourseInstance data to private attributes>>
|
|
1148
|
+
|
|
1149
|
+
self.__name = data.pop("Benamning", None)
|
|
1150
|
+
self.__code = data.pop("Utbildningskod", None)
|
|
1151
|
+
|
|
1152
|
+
self.__credits = data.pop("Omfattning", None)
|
|
1153
|
+
self.__unit = data.pop("Enhet", None)
|
|
1154
|
+
|
|
1155
|
+
self.__version = data.pop("Versionsnummer", None)
|
|
1156
|
+
|
|
1157
|
+
self.__education_id = data.pop("UtbildningUID", None)
|
|
1158
|
+
|
|
1159
|
+
try:
|
|
1160
|
+
self.__grade_scale = self.ladok.get_grade_scales(
|
|
1161
|
+
id=data.pop("BetygsskalaID"))
|
|
1162
|
+
except KeyError:
|
|
1163
|
+
self.__grade_scale = None
|
|
1164
|
+
@
|
|
1165
|
+
|
|
1166
|
+
Finally, we want to have properties to access the private attributes.
|
|
1167
|
+
<<CourseInstance methods>>=
|
|
1058
1168
|
@property
|
|
1059
1169
|
def instance_id(self):
|
|
1060
1170
|
return self.__instance_id
|
|
@@ -1092,57 +1202,6 @@ def components(self, /, **kwargs):
|
|
|
1092
1202
|
return filter_on_keys(self.__components, **kwargs)
|
|
1093
1203
|
@
|
|
1094
1204
|
|
|
1095
|
-
Now we must fetch data from LADOK.
|
|
1096
|
-
<<fetch CourseInstance data object from LADOK>>=
|
|
1097
|
-
data = self.ladok.course_round_components_JSON(self.round_id)
|
|
1098
|
-
@ Then data will be populated with the following values:
|
|
1099
|
-
\begin{pycode}
|
|
1100
|
-
import json
|
|
1101
|
-
import ladok3
|
|
1102
|
-
import os
|
|
1103
|
-
|
|
1104
|
-
ladok = ladok3.LadokSession(
|
|
1105
|
-
os.environ["LADOK_INST"],
|
|
1106
|
-
vars={"username": os.environ["LADOK_USER"],
|
|
1107
|
-
"password": os.environ["LADOK_PASS"]},
|
|
1108
|
-
test_environment=True)
|
|
1109
|
-
|
|
1110
|
-
print(r"\begin{minted}{JSON}")
|
|
1111
|
-
data = ladok.course_round_components_JSON(
|
|
1112
|
-
"cf7045a7-3e1c-11eb-b960-5f936a674375")
|
|
1113
|
-
ladok3.clean_data(data)
|
|
1114
|
-
print(json.dumps(data, indent=2))
|
|
1115
|
-
print(r"\end{minted}")
|
|
1116
|
-
\end{pycode}
|
|
1117
|
-
|
|
1118
|
-
Now that we have the [[data]] object, we can assign its values to the private
|
|
1119
|
-
attributes.
|
|
1120
|
-
We note, however, that some course instances lack both [[Versionsnummer]] and
|
|
1121
|
-
[[Omfattning]].
|
|
1122
|
-
It seems like faux courses are created to document when students go on
|
|
1123
|
-
Exchanges.
|
|
1124
|
-
These faux courses have a name and code, but no credits or version of syllabus.
|
|
1125
|
-
<<assign CourseInstance data to private attributes>>=
|
|
1126
|
-
self.__education_id = data.pop("UtbildningUID")
|
|
1127
|
-
|
|
1128
|
-
self.__code = data.pop("Utbildningskod")
|
|
1129
|
-
self.__name = data.pop("Benamning")
|
|
1130
|
-
self.__version = data.pop("Versionsnummer", None)
|
|
1131
|
-
|
|
1132
|
-
self.__credits = data.pop("Omfattning", None)
|
|
1133
|
-
self.__unit = data.pop("Enhet", None)
|
|
1134
|
-
|
|
1135
|
-
self.__grade_scale = self.ladok.get_grade_scales(
|
|
1136
|
-
id=data.pop("BetygsskalaID"))
|
|
1137
|
-
|
|
1138
|
-
if "IngaendeMoment" in data:
|
|
1139
|
-
self.__components = [CourseComponent(
|
|
1140
|
-
ladok=self.ladok, course=self,
|
|
1141
|
-
**component) for component in data["IngaendeMoment"]]
|
|
1142
|
-
else:
|
|
1143
|
-
self.__components = []
|
|
1144
|
-
@
|
|
1145
|
-
|
|
1146
1205
|
|
|
1147
1206
|
\section{Course components}
|
|
1148
1207
|
|
ladok3/report.nw
CHANGED
|
@@ -133,7 +133,7 @@ Finally, we have the list of graders.
|
|
|
133
133
|
<<add one result group arguments>>=
|
|
134
134
|
one_parser.add_argument("graders", nargs="*",
|
|
135
135
|
help="Space separated list of who did the grading, "
|
|
136
|
-
"give each grader as 'First Last <email@
|
|
136
|
+
"give each grader as 'First Last <email@institution.se>'.")
|
|
137
137
|
@
|
|
138
138
|
|
|
139
139
|
Now that we have the arguments, we can just execute the following code using
|