ladok3 4.13__py3-none-any.whl → 5.4__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/Makefile +6 -0
- ladok3/__init__.py +1443 -3350
- ladok3/api.nw +1649 -224
- ladok3/cli.nw +102 -53
- ladok3/cli.py +84 -35
- ladok3/data.nw +92 -15
- ladok3/data.py +79 -3
- ladok3/ladok.bash +35 -17
- ladok3/ladok3.nw +242 -9
- ladok3/report.nw +183 -117
- ladok3/report.py +135 -63
- ladok3/scripts.nw +244 -0
- ladok3/student.nw +69 -4
- ladok3/student.py +98 -42
- ladok3/undoc.nw +62 -3119
- {ladok3-4.13.dist-info → ladok3-5.4.dist-info}/LICENSE +1 -1
- {ladok3-4.13.dist-info → ladok3-5.4.dist-info}/METADATA +39 -17
- ladok3-5.4.dist-info/RECORD +21 -0
- {ladok3-4.13.dist-info → ladok3-5.4.dist-info}/WHEEL +1 -1
- ladok3/.gitignore +0 -10
- ladok3-4.13.dist-info/RECORD +0 -21
- {ladok3-4.13.dist-info → ladok3-5.4.dist-info}/entry_points.txt +0 -0
ladok3/api.nw
CHANGED
|
@@ -16,13 +16,11 @@ We will use the following to test the API methods.
|
|
|
16
16
|
<<test api.py>>=
|
|
17
17
|
import json
|
|
18
18
|
import ladok3
|
|
19
|
+
import ladok3.cli
|
|
19
20
|
import os
|
|
20
21
|
|
|
21
|
-
ladok = ladok3.LadokSession(
|
|
22
|
-
|
|
23
|
-
vars={"username": os.environ["LADOK_USER"],
|
|
24
|
-
"password": os.environ["LADOK_PASS"]},
|
|
25
|
-
test_environment=True) # for experiments
|
|
22
|
+
ladok = ladok3.LadokSession(*ladok3.cli.load_credentials(),
|
|
23
|
+
test_environment=True) # for experiments
|
|
26
24
|
|
|
27
25
|
student_uid = "de709f81-a867-11e7-8dbf-78e86dc2470c"
|
|
28
26
|
dasak_instance_id = "39c56d6a-73d8-11e8-b4e0-063f9afb40e3"
|
|
@@ -41,13 +39,11 @@ This is useful for development and when LADOK changes anything in the API.
|
|
|
41
39
|
\begin{pycode}[apitest]
|
|
42
40
|
import json
|
|
43
41
|
import ladok3
|
|
42
|
+
import ladok3.cli
|
|
44
43
|
import os
|
|
45
44
|
|
|
46
|
-
ladok = ladok3.LadokSession(
|
|
47
|
-
|
|
48
|
-
vars={"username": os.environ["LADOK_USER"],
|
|
49
|
-
"password": os.environ["LADOK_PASS"]},
|
|
50
|
-
test_environment=True) # for experiments
|
|
45
|
+
ladok = ladok3.LadokSession(*ladok3.cli.load_credentials(),
|
|
46
|
+
test_environment=True) # for experiments
|
|
51
47
|
|
|
52
48
|
student_uid = "de709f81-a867-11e7-8dbf-78e86dc2470c"
|
|
53
49
|
dasak_instance_id = "39c56d6a-73d8-11e8-b4e0-063f9afb40e3"
|
|
@@ -59,6 +55,142 @@ KTH_org_id = "2474f616-dc41-11e8-8cc1-eaeeb71b497f"
|
|
|
59
55
|
\end{pycode}
|
|
60
56
|
|
|
61
57
|
|
|
58
|
+
\section{Exception classes}\label{ExceptionClasses}
|
|
59
|
+
|
|
60
|
+
LADOK operations can fail in various ways, and it's important to handle these
|
|
61
|
+
failures gracefully with meaningful error messages. To provide better error
|
|
62
|
+
handling and debugging capabilities, we define a hierarchy of custom exception
|
|
63
|
+
classes that represent different types of errors that can occur when working
|
|
64
|
+
with the LADOK API.
|
|
65
|
+
|
|
66
|
+
All custom exceptions inherit from a base [[LadokError]] class, which in turn
|
|
67
|
+
inherits from Python's built-in [[Exception]] class. This ensures backward
|
|
68
|
+
compatibility while providing enhanced error handling capabilities.
|
|
69
|
+
|
|
70
|
+
\subsection{Exception class hierarchy}
|
|
71
|
+
|
|
72
|
+
The exception hierarchy is designed to allow applications to catch specific
|
|
73
|
+
types of errors while still maintaining the ability to catch all LADOK-related
|
|
74
|
+
errors with a single exception handler.
|
|
75
|
+
|
|
76
|
+
\begin{description}
|
|
77
|
+
\item[{[[LadokError]]}] Base exception class for all LADOK-related errors.
|
|
78
|
+
\item[{[[LadokServerError]]}] For server-side LADOK errors (uses
|
|
79
|
+
[[response.json()["Meddelande"]]]).
|
|
80
|
+
\item[{[[LadokValidationError]]}] For data validation errors (invalid person
|
|
81
|
+
numbers, grades, etc.).
|
|
82
|
+
\item[{[[LadokAPIError]]}] For API/HTTP related errors (network failures,
|
|
83
|
+
unexpected responses).
|
|
84
|
+
\item[{[[LadokNotFoundError]]}] For resource not found errors (missing courses,
|
|
85
|
+
components, etc.).
|
|
86
|
+
\end{description}
|
|
87
|
+
|
|
88
|
+
Usage example:
|
|
89
|
+
\begin{minted}{python}
|
|
90
|
+
import ladok3
|
|
91
|
+
import ladok3.cli
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
session = ladok3.LadokSession(**ladok3.cli.load_credentials(),
|
|
95
|
+
test_environment=True)
|
|
96
|
+
student = session.get_student("invalid-person-nr")
|
|
97
|
+
except ladok3.LadokValidationError as e:
|
|
98
|
+
print(f"Invalid data provided: {e}")
|
|
99
|
+
except ladok3.LadokServerError as e:
|
|
100
|
+
print(f"LADOK server returned error: {e}")
|
|
101
|
+
except ladok3.LadokAPIError as e:
|
|
102
|
+
print(f"Network/API failure: {e}")
|
|
103
|
+
except ladok3.LadokError as e:
|
|
104
|
+
print(f"General LADOK error: {e}")
|
|
105
|
+
\end{minted}
|
|
106
|
+
This example should yield at least a validation error, since we supply an
|
|
107
|
+
invalid personnummer to [[.get_student]].
|
|
108
|
+
|
|
109
|
+
\subsection{Exception class definitions}
|
|
110
|
+
|
|
111
|
+
The exception classes are defined as follows:
|
|
112
|
+
<<classes>>=
|
|
113
|
+
class LadokError(Exception):
|
|
114
|
+
"""
|
|
115
|
+
Base exception class for all LADOK-related errors.
|
|
116
|
+
|
|
117
|
+
This is the parent class for all LADOK-specific exceptions. It can be used
|
|
118
|
+
to catch any LADOK-related error in a single exception handler while still
|
|
119
|
+
allowing more specific error handling when needed.
|
|
120
|
+
|
|
121
|
+
All other LADOK exception classes inherit from this class, ensuring
|
|
122
|
+
backward compatibility with existing code that catches generic exceptions.
|
|
123
|
+
"""
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
class LadokServerError(LadokError):
|
|
127
|
+
"""
|
|
128
|
+
Exception for server-side LADOK errors.
|
|
129
|
+
|
|
130
|
+
This exception is raised when the LADOK server returns an error response,
|
|
131
|
+
typically containing an error message in the response JSON under the
|
|
132
|
+
"Meddelande" key. This indicates that the request reached the server
|
|
133
|
+
successfully, but the server was unable to process it due to business
|
|
134
|
+
logic constraints or data validation issues on the server side.
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
- Attempting to register a grade for a student not enrolled in the course
|
|
138
|
+
- Trying to access data that the authenticated user doesn't have permission to view
|
|
139
|
+
- Server-side validation failures
|
|
140
|
+
"""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
class LadokValidationError(LadokError):
|
|
144
|
+
"""
|
|
145
|
+
Exception for data validation errors.
|
|
146
|
+
|
|
147
|
+
This exception is raised when input data fails validation before being
|
|
148
|
+
sent to the LADOK API. This includes format validation, business rule
|
|
149
|
+
validation, and other client-side checks that can be performed without
|
|
150
|
+
contacting the server.
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
- Invalid Swedish personal numbers (personnummer)
|
|
154
|
+
- Invalid grade values for a specific grading scale
|
|
155
|
+
- Missing required fields
|
|
156
|
+
- Date format errors
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
class LadokAPIError(LadokError):
|
|
161
|
+
"""
|
|
162
|
+
Exception for API/HTTP related errors.
|
|
163
|
+
|
|
164
|
+
This exception is raised when there are problems with the HTTP communication
|
|
165
|
+
to the LADOK API or when the API returns unexpected responses. This includes
|
|
166
|
+
network connectivity issues, HTTP protocol errors, and malformed responses.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
- Network connectivity failures
|
|
170
|
+
- HTTP status codes indicating client or server errors
|
|
171
|
+
- Malformed JSON responses
|
|
172
|
+
- Authentication failures
|
|
173
|
+
- Timeouts
|
|
174
|
+
"""
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
class LadokNotFoundError(LadokError):
|
|
178
|
+
"""
|
|
179
|
+
Exception for resource not found errors.
|
|
180
|
+
|
|
181
|
+
This exception is raised when a requested resource cannot be found in LADOK.
|
|
182
|
+
This is typically used for HTTP 404 responses or when searching for entities
|
|
183
|
+
that don't exist.
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
- Course instances that don't exist
|
|
187
|
+
- Students not found in the system
|
|
188
|
+
- Course components that are not part of a course
|
|
189
|
+
- Non-existent course rounds
|
|
190
|
+
"""
|
|
191
|
+
pass
|
|
192
|
+
@
|
|
193
|
+
|
|
62
194
|
\section{HTTP queries to LADOK}
|
|
63
195
|
|
|
64
196
|
We will make all queries to LADOK over HTTP as they provide a REST API.
|
|
@@ -78,7 +210,7 @@ To run against the test environment, change the base URL to
|
|
|
78
210
|
\end{center}
|
|
79
211
|
<<LadokSession constructor body>>=
|
|
80
212
|
self.base_url = "https://www.start.ladok.se" if not test_environment \
|
|
81
|
-
|
|
213
|
+
else "https://www.test.ladok.se"
|
|
82
214
|
self.base_gui_url = self.base_url + "/gui"
|
|
83
215
|
self.base_gui_proxy_url = self.base_gui_url + "/proxy"
|
|
84
216
|
@
|
|
@@ -90,9 +222,12 @@ application/vnd.ladok-kataloginformation+json, \
|
|
|
90
222
|
application/vnd.ladok-studentinformation+json, \
|
|
91
223
|
application/vnd.ladok-studiedeltagande+json, \
|
|
92
224
|
application/vnd.ladok-utbildningsinformation+json, \
|
|
93
|
-
application/vnd.ladok-examen+json,
|
|
94
|
-
application/vnd.ladok-
|
|
95
|
-
application/json,
|
|
225
|
+
application/vnd.ladok-examen+json, \
|
|
226
|
+
application/vnd.ladok-extintegration+json, \
|
|
227
|
+
application/vnd.ladok-uppfoljning+json, \
|
|
228
|
+
application/vnd.ladok-extra+json, \
|
|
229
|
+
application/json, \
|
|
230
|
+
text/plain'}
|
|
96
231
|
@
|
|
97
232
|
|
|
98
233
|
|
|
@@ -107,51 +242,147 @@ freshness of the XSRF token.
|
|
|
107
242
|
The other requests track that by the use of the XSRF token itself.
|
|
108
243
|
We'll get back to this in \cref{XSRFtoken}.
|
|
109
244
|
<<LadokSession data methods>>=
|
|
110
|
-
def get_query(self, path,
|
|
111
|
-
|
|
245
|
+
def get_query(self, path,
|
|
246
|
+
content_type="application/vnd.ladok-resultat+json"):
|
|
247
|
+
"""
|
|
248
|
+
Make GET query to LADOK server and return JSON data.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
path: API endpoint path
|
|
252
|
+
content_type: HTTP Content-Type header value
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
JSON data from the response
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
LadokServerError: If the server returns an error message
|
|
259
|
+
LadokAPIError: If the request fails or returns an error status
|
|
260
|
+
"""
|
|
112
261
|
headers = self.headers.copy()
|
|
113
262
|
headers["Content-Type"] = content_type
|
|
114
263
|
|
|
115
264
|
<<record time of request>>
|
|
116
265
|
|
|
117
|
-
|
|
266
|
+
response = self.session.get(
|
|
118
267
|
url=self.base_gui_proxy_url + path,
|
|
119
268
|
headers=headers)
|
|
269
|
+
|
|
270
|
+
if response.ok:
|
|
271
|
+
return response.json()
|
|
272
|
+
try:
|
|
273
|
+
error_msg = response.json()["Meddelande"]
|
|
274
|
+
raise LadokServerError(error_msg)
|
|
275
|
+
except:
|
|
276
|
+
error_msg = response.text
|
|
277
|
+
raise LadokAPIError(f"GET request to {path} failed: {error_msg}")
|
|
120
278
|
|
|
121
279
|
def put_query(self, path, put_data,
|
|
122
|
-
|
|
123
|
-
"""
|
|
280
|
+
content_type="application/vnd.ladok-resultat+json"):
|
|
281
|
+
"""
|
|
282
|
+
Make PUT query to LADOK server and return JSON data.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
path: API endpoint path
|
|
286
|
+
put_data: Data to send in request body
|
|
287
|
+
content_type: HTTP Content-Type header value
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
JSON data from the response
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
LadokServerError: If the server returns an error message
|
|
294
|
+
LadokAPIError: If the request fails or returns an error status
|
|
295
|
+
"""
|
|
124
296
|
headers = self.headers.copy()
|
|
125
297
|
headers["Content-Type"] = content_type
|
|
126
298
|
headers["X-XSRF-TOKEN"] = self.xsrf_token
|
|
127
299
|
headers["Referer"] = self.base_gui_url
|
|
128
300
|
|
|
129
|
-
|
|
301
|
+
response = self.session.put(
|
|
130
302
|
url=self.base_gui_proxy_url + path,
|
|
131
303
|
json=put_data,
|
|
132
304
|
headers=headers)
|
|
305
|
+
|
|
306
|
+
if response.ok:
|
|
307
|
+
return response.json()
|
|
308
|
+
try:
|
|
309
|
+
error_msg = response.json()["Meddelande"]
|
|
310
|
+
raise LadokServerError(error_msg)
|
|
311
|
+
except:
|
|
312
|
+
error_msg = response.text
|
|
313
|
+
raise LadokAPIError(f"PUT request to {path} failed: {error_msg}")
|
|
133
314
|
|
|
134
315
|
def post_query(self, path, post_data,
|
|
135
|
-
|
|
136
|
-
"""
|
|
316
|
+
content_type="application/vnd.ladok-resultat+json"):
|
|
317
|
+
"""
|
|
318
|
+
Make POST query to LADOK server and return JSON data.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
path: API endpoint path
|
|
322
|
+
post_data: Data to send in request body
|
|
323
|
+
content_type: HTTP Content-Type header value
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
JSON data from the response
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
LadokServerError: If the server returns an error message
|
|
330
|
+
LadokAPIError: If the request fails or returns an error status
|
|
331
|
+
"""
|
|
137
332
|
headers = self.headers.copy()
|
|
138
333
|
headers["Content-Type"] = content_type
|
|
139
334
|
headers["X-XSRF-TOKEN"] = self.xsrf_token
|
|
140
335
|
headers["Referer"] = self.base_gui_url
|
|
141
336
|
|
|
142
|
-
|
|
337
|
+
response = self.session.post(
|
|
143
338
|
url=self.base_gui_proxy_url + path,
|
|
144
339
|
json=post_data,
|
|
145
340
|
headers=headers)
|
|
341
|
+
|
|
342
|
+
if response.ok:
|
|
343
|
+
return response.json()
|
|
344
|
+
try:
|
|
345
|
+
error_msg = response.json()["Meddelande"]
|
|
346
|
+
raise LadokServerError(error_msg)
|
|
347
|
+
except:
|
|
348
|
+
error_msg = response.text
|
|
349
|
+
raise LadokAPIError(f"POST request to {path} failed: {error_msg}")
|
|
146
350
|
|
|
147
351
|
def del_query(self, path):
|
|
148
|
-
"""
|
|
352
|
+
"""
|
|
353
|
+
Returns DELETE query response for path on the LADOK server.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
path (str): API endpoint path
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
True, if success but no content is returned.
|
|
360
|
+
JSON data from the response, if any.
|
|
361
|
+
Otherwise the response object.
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
LadokServerError: If the server returns an error message
|
|
365
|
+
LadokAPIError: If the request fails or returns an error status
|
|
366
|
+
"""
|
|
149
367
|
headers = self.headers.copy()
|
|
150
368
|
headers["X-XSRF-TOKEN"] = self.xsrf_token
|
|
151
369
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
370
|
+
response = self.session.delete(url=self.base_gui_proxy_url+path,
|
|
371
|
+
headers=headers)
|
|
372
|
+
|
|
373
|
+
if response.status_code == requests.codes.no_content:
|
|
374
|
+
return True
|
|
375
|
+
if response.ok:
|
|
376
|
+
try:
|
|
377
|
+
return response.json()
|
|
378
|
+
except:
|
|
379
|
+
return response
|
|
380
|
+
try:
|
|
381
|
+
error_msg = response.json()["Meddelande"]
|
|
382
|
+
raise LadokServerError(error_msg)
|
|
383
|
+
except:
|
|
384
|
+
error_msg = response.text
|
|
385
|
+
raise LadokAPIError(f"DELETE request to {path} failed: {error_msg}")
|
|
155
386
|
@
|
|
156
387
|
|
|
157
388
|
\subsection{The XSRF token}\label{XSRFtoken}
|
|
@@ -166,6 +397,15 @@ invalidated by LADOK.
|
|
|
166
397
|
<<LadokSession data methods>>=
|
|
167
398
|
@property
|
|
168
399
|
def xsrf_token(self):
|
|
400
|
+
"""
|
|
401
|
+
Get a fresh XSRF token for authenticated requests.
|
|
402
|
+
|
|
403
|
+
LADOK requires XSRF tokens for PUT, POST, and DELETE requests. This property
|
|
404
|
+
ensures the token is fresh and valid, triggering a re-login if necessary.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
str: A valid XSRF token for use in authenticated requests.
|
|
408
|
+
"""
|
|
169
409
|
<<ensure the XSRF token is fresh>>
|
|
170
410
|
|
|
171
411
|
cookies = self.session.cookies.get_dict()
|
|
@@ -195,8 +435,8 @@ POST or DEL request will soon follow.
|
|
|
195
435
|
Hence, we can update the time of the last request whenever the XSRF token is
|
|
196
436
|
read.
|
|
197
437
|
<<ensure the XSRF token is fresh>>=
|
|
198
|
-
if not self.__access_time
|
|
199
|
-
|
|
438
|
+
if (not self.__access_time
|
|
439
|
+
or datetime.datetime.now()-self.__access_time > self.__timeout):
|
|
200
440
|
self.user_info_JSON() # trigger login
|
|
201
441
|
else:
|
|
202
442
|
<<record time of request>>
|
|
@@ -212,6 +452,15 @@ These recursively transcends the JSON structure removing the data that should
|
|
|
212
452
|
be removed.
|
|
213
453
|
<<functions>>=
|
|
214
454
|
def clean_data(json_obj):
|
|
455
|
+
"""
|
|
456
|
+
Clean a JSON object by removing internal links and pseudonymizing personal data.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
json_obj (dict or list): The JSON data to clean.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
dict or list: The cleaned JSON object.
|
|
463
|
+
"""
|
|
215
464
|
remove_links(json_obj)
|
|
216
465
|
pseudonymize(json_obj)
|
|
217
466
|
return json_obj
|
|
@@ -221,7 +470,9 @@ The [[remove_links]] functions removes the [[link]] key--value pairs.
|
|
|
221
470
|
The [[link]] values contains URLs for all requests that data are based on.
|
|
222
471
|
<<functions>>=
|
|
223
472
|
def remove_links(json_obj):
|
|
224
|
-
"""
|
|
473
|
+
"""
|
|
474
|
+
Recursively removes all "link" keys and values
|
|
475
|
+
"""
|
|
225
476
|
if isinstance(json_obj, dict):
|
|
226
477
|
if "link" in json_obj:
|
|
227
478
|
json_obj.pop("link")
|
|
@@ -236,7 +487,9 @@ The [[pseudonymize]] function replaces names and personnummer with dummy
|
|
|
236
487
|
entries.
|
|
237
488
|
<<functions>>=
|
|
238
489
|
def pseudonymize(json_obj):
|
|
239
|
-
"""
|
|
490
|
+
"""
|
|
491
|
+
Recursively pseudonymizes a JSON data record
|
|
492
|
+
"""
|
|
240
493
|
if isinstance(json_obj, dict):
|
|
241
494
|
if "Fornamn" in json_obj:
|
|
242
495
|
json_obj["Fornamn"] = "Student"
|
|
@@ -272,13 +525,26 @@ To request the grading scales from LADOK, we request all of them and return a
|
|
|
272
525
|
list of JSON data objects containing the grading scale data.
|
|
273
526
|
<<LadokSession data methods>>=
|
|
274
527
|
def grade_scales_JSON(self):
|
|
275
|
-
|
|
528
|
+
"""
|
|
529
|
+
Fetch all grading scales from LADOK.
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
list: A list of JSON objects containing grading scale data from LADOK.
|
|
533
|
+
|
|
534
|
+
Raises:
|
|
535
|
+
Exception: If the request fails or returns an error status.
|
|
536
|
+
"""
|
|
537
|
+
data = self.get_query(
|
|
276
538
|
"/kataloginformation/internal/grunddata/betygsskala",
|
|
277
|
-
content_type="application/vnd.ladok-kataloginformation+json;charset=UTF-8"
|
|
539
|
+
content_type="application/vnd.ladok-kataloginformation+json;charset=UTF-8"
|
|
540
|
+
)
|
|
278
541
|
|
|
279
|
-
|
|
280
|
-
return
|
|
281
|
-
|
|
542
|
+
try:
|
|
543
|
+
return data["Betygsskala"]
|
|
544
|
+
except KeyError as err:
|
|
545
|
+
err.add_note(f"Response data: {data}")
|
|
546
|
+
raise LadokAPIError(f"Unexpected response format when fetching grading scales: "
|
|
547
|
+
f"missing 'Betygsskala' key") from err
|
|
282
548
|
@
|
|
283
549
|
|
|
284
550
|
We add the following test.
|
|
@@ -322,9 +588,23 @@ of Chip.)
|
|
|
322
588
|
#
|
|
323
589
|
# RETURNERAR en dictionary med för- och efternamn and more
|
|
324
590
|
def get_student_data_JSON(self, person_nr_raw, lang = 'sv'):
|
|
591
|
+
"""
|
|
592
|
+
Get student data from LADOK using a Swedish personal number (personnummer).
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
person_nr_raw (str): Swedish personal number in format YYYYMMDD-XXXX or similar
|
|
596
|
+
lang (str, optional): Language code 'en' or 'sv'. Defaults to 'sv'.
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
dict: Student data dictionary containing name, contact info, and more.
|
|
600
|
+
|
|
601
|
+
Raises:
|
|
602
|
+
Exception: If the personal number format is invalid.
|
|
603
|
+
ValueError: If student cannot be found or multiple matches found.
|
|
604
|
+
"""
|
|
325
605
|
person_nr = format_personnummer(person_nr_raw)
|
|
326
606
|
|
|
327
|
-
if not person_nr: raise
|
|
607
|
+
if not person_nr: raise LadokValidationError('Invalid person nr ' + person_nr_raw)
|
|
328
608
|
|
|
329
609
|
response = self.session.get(
|
|
330
610
|
url=self.base_gui_proxy_url +
|
|
@@ -367,13 +647,23 @@ directly.
|
|
|
367
647
|
#
|
|
368
648
|
# RETURNERAR en dictionary med för- och efternamn and more
|
|
369
649
|
def get_student_data_by_uid_JSON(self, uid):
|
|
370
|
-
|
|
650
|
+
"""
|
|
651
|
+
Get student data from LADOK using a LADOK UID.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
uid (str): The student's unique LADOK identifier.
|
|
655
|
+
|
|
656
|
+
Returns:
|
|
657
|
+
dict: Student data dictionary containing personal information.
|
|
658
|
+
|
|
659
|
+
Raises:
|
|
660
|
+
AttributeError: If student cannot be found by the given UID.
|
|
661
|
+
"""
|
|
662
|
+
data = self.get_query(
|
|
371
663
|
f"/studentinformation/internal/student/{uid}",
|
|
372
664
|
content_type="application/vnd.ladok-studentinformation+json;charset=UTF-8")
|
|
373
665
|
|
|
374
|
-
|
|
375
|
-
return response.json()
|
|
376
|
-
raise AttributeError(f"can't fetch student attributes by LADOK ID {uid}")
|
|
666
|
+
return data
|
|
377
667
|
@
|
|
378
668
|
|
|
379
669
|
To test this function, we do the following.
|
|
@@ -398,15 +688,20 @@ This data includes email, postal address and phone number.
|
|
|
398
688
|
It also includes when they were last updated.
|
|
399
689
|
<<LadokSession data methods>>=
|
|
400
690
|
def get_student_contact_data_JSON(self, student_id):
|
|
401
|
-
"""
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
691
|
+
"""
|
|
692
|
+
Returns contact data for student with student_id, returns JSON
|
|
693
|
+
"""
|
|
694
|
+
try:
|
|
695
|
+
return self.get_query(
|
|
696
|
+
f"/studentinformation/internal/student/{student_id}/kontaktuppgifter",
|
|
697
|
+
"application/vnd.ladok-studentinformation+json"
|
|
698
|
+
)
|
|
699
|
+
except LadokAPIError as err:
|
|
700
|
+
raise LadokAPIError(f"Failed to get contact data for "
|
|
701
|
+
f"student {student_id}: {err}") from err
|
|
702
|
+
except LadokServerError as err:
|
|
703
|
+
raise LadokServerError(f"LADOK server error when getting contact data for "
|
|
704
|
+
f"student {student_id}: {err}") from err
|
|
410
705
|
@
|
|
411
706
|
|
|
412
707
|
We test this function.
|
|
@@ -434,14 +729,17 @@ def get_student_suspensions_JSON(self, student_id):
|
|
|
434
729
|
Returns suspensions from studies for student with student_id,
|
|
435
730
|
returns JSON
|
|
436
731
|
"""
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
732
|
+
try:
|
|
733
|
+
return self.get_query(
|
|
734
|
+
f"/studentinformation/internal/avstangning/student/{student_id}",
|
|
735
|
+
"application/vnd.ladok-studentinformation+json"
|
|
736
|
+
)
|
|
737
|
+
except LadokAPIError as err:
|
|
738
|
+
raise LadokAPIError(f"Failed to get suspensions for "
|
|
739
|
+
f"student {student_id}: {err}") from err
|
|
740
|
+
except LadokServerError as err:
|
|
741
|
+
raise LadokServerError(f"LADOK server error when getting suspensions for "
|
|
742
|
+
f"student {student_id}: {err}") from err
|
|
445
743
|
@
|
|
446
744
|
|
|
447
745
|
We test this function.
|
|
@@ -466,16 +764,21 @@ This methods returns \emph{all} registrations for a student, \ie registrations
|
|
|
466
764
|
on courses and programmes.
|
|
467
765
|
<<LadokSession data methods>>=
|
|
468
766
|
def registrations_JSON(self, student_id):
|
|
469
|
-
"""
|
|
470
|
-
|
|
767
|
+
"""
|
|
768
|
+
Return all registrations for student with ID student_id.
|
|
769
|
+
"""
|
|
770
|
+
data = self.get_query(
|
|
471
771
|
"/studiedeltagande/internal/tillfallesdeltagande/kurstillfallesdeltagande"
|
|
472
772
|
f"/student/{student_id}",
|
|
473
|
-
"application/vnd.ladok-studiedeltagande+json"
|
|
773
|
+
"application/vnd.ladok-studiedeltagande+json"
|
|
774
|
+
)
|
|
474
775
|
|
|
475
|
-
|
|
476
|
-
return
|
|
477
|
-
|
|
478
|
-
|
|
776
|
+
try:
|
|
777
|
+
return data["Tillfallesdeltaganden"]
|
|
778
|
+
except KeyError as err:
|
|
779
|
+
err.add_note(f"Response data: {data}")
|
|
780
|
+
raise LadokAPIError(f"Unexpected response format when fetching registrations for "
|
|
781
|
+
f"student {student_id}: missing 'Tillfallesdeltaganden' key") from err
|
|
479
782
|
@
|
|
480
783
|
|
|
481
784
|
We provide the following test.
|
|
@@ -502,20 +805,24 @@ student.
|
|
|
502
805
|
This way we can check if a student has been registered several times on a
|
|
503
806
|
course.
|
|
504
807
|
<<LadokSession data methods>>=
|
|
505
|
-
def registrations_on_course_JSON(self,
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
808
|
+
def registrations_on_course_JSON(self, course_education_id, student_id):
|
|
809
|
+
"""
|
|
810
|
+
Return a list of registrations on course with education_id for student with
|
|
811
|
+
student_id. JSON format.
|
|
812
|
+
"""
|
|
813
|
+
data = self.get_query(
|
|
510
814
|
"/studiedeltagande/internal/tillfallesdeltagande"
|
|
511
815
|
f"/utbildning/{course_education_id}/student/{student_id}",
|
|
512
|
-
"application/vnd.ladok-studiedeltagande+json"
|
|
816
|
+
"application/vnd.ladok-studiedeltagande+json"
|
|
817
|
+
)
|
|
513
818
|
|
|
514
|
-
|
|
515
|
-
return
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
819
|
+
try:
|
|
820
|
+
return data["Tillfallesdeltaganden"]
|
|
821
|
+
except KeyError as err:
|
|
822
|
+
err.add_note(f"Response data: {data}")
|
|
823
|
+
raise LadokAPIError(f"Unexpected response format when fetching registrations for "
|
|
824
|
+
f"student {student_id} on course {course_education_id}: "
|
|
825
|
+
f"missing 'Tillfallesdeltaganden' key") from err
|
|
519
826
|
@
|
|
520
827
|
|
|
521
828
|
We add the following test.
|
|
@@ -534,6 +841,43 @@ print(r"\end{minted}")
|
|
|
534
841
|
\end{pycode}
|
|
535
842
|
|
|
536
843
|
|
|
844
|
+
\section{[[studystructure_student_JSON]]}
|
|
845
|
+
|
|
846
|
+
We also want to get a student's study structure, that is programmes that they
|
|
847
|
+
are admitted to.
|
|
848
|
+
<<LadokSession data methods>>=
|
|
849
|
+
# added by GQMJr
|
|
850
|
+
def studystructure_student_JSON(self, uid):
|
|
851
|
+
"""
|
|
852
|
+
Returns a dictionary of student information. This contains programmes that
|
|
853
|
+
the student is admitted to.
|
|
854
|
+
"""
|
|
855
|
+
r = self.session.get(
|
|
856
|
+
url=self.base_gui_proxy_url +
|
|
857
|
+
'/studiedeltagande/internal/studiestruktur/student/'+uid,
|
|
858
|
+
headers=self.headers)
|
|
859
|
+
if r.status_code == 200:
|
|
860
|
+
return r.json()
|
|
861
|
+
raise LadokAPIError(f"can't get study structure for student {uid}: {r.text}")
|
|
862
|
+
@
|
|
863
|
+
|
|
864
|
+
Let's add a test.
|
|
865
|
+
This should return a dictionary.
|
|
866
|
+
<<test functions>>=
|
|
867
|
+
def test_studystructure_student_JSON():
|
|
868
|
+
r = ladok.studystructure_student_JSON(student_uid)
|
|
869
|
+
assert type(r) == dict
|
|
870
|
+
@
|
|
871
|
+
|
|
872
|
+
The output looks like this:
|
|
873
|
+
\begin{pycode}[apitest]
|
|
874
|
+
print(r"\begin{minted}{JSON}")
|
|
875
|
+
print(json.dumps(ladok3.clean_data(ladok.studystructure_student_JSON(student_uid)),
|
|
876
|
+
indent=2))
|
|
877
|
+
print(r"\end{minted}")
|
|
878
|
+
\end{pycode}
|
|
879
|
+
|
|
880
|
+
|
|
537
881
|
|
|
538
882
|
\chapter{Course-related API calls}
|
|
539
883
|
|
|
@@ -556,11 +900,14 @@ def search_course_rounds_JSON(self, /, **kwargs):
|
|
|
556
900
|
|
|
557
901
|
url += "page=1&limit=400&sprakkod=sv"
|
|
558
902
|
|
|
559
|
-
|
|
903
|
+
data = self.get_query(url)
|
|
560
904
|
|
|
561
|
-
|
|
562
|
-
return
|
|
563
|
-
|
|
905
|
+
try:
|
|
906
|
+
return data["Resultat"]
|
|
907
|
+
except KeyError as err:
|
|
908
|
+
err.add_note(f"Response data: {data}")
|
|
909
|
+
raise LadokAPIError(f"Unexpected response format when searching for course rounds: "
|
|
910
|
+
f"missing 'Resultat' key") from err
|
|
564
911
|
@
|
|
565
912
|
|
|
566
913
|
We add the following test.
|
|
@@ -573,9 +920,9 @@ def test_search_course_rounds_JSON():
|
|
|
573
920
|
The output looks like this.
|
|
574
921
|
\begin{pycode}[apitest]
|
|
575
922
|
print(r"\begin{minted}{JSON}")
|
|
576
|
-
|
|
577
|
-
ladok3.clean_data(
|
|
578
|
-
print(json.dumps(
|
|
923
|
+
course_rounds = ladok.search_course_rounds_JSON(code="DD1317")
|
|
924
|
+
ladok3.clean_data(course_rounds)
|
|
925
|
+
print(json.dumps(course_rounds, indent=2, ensure_ascii=False))
|
|
579
926
|
print(r"\end{minted}")
|
|
580
927
|
\end{pycode}
|
|
581
928
|
|
|
@@ -587,13 +934,16 @@ This method fetches all course rounds that uses the given course instance.
|
|
|
587
934
|
<<LadokSession data methods>>=
|
|
588
935
|
def course_rounds_JSON(self, course_instance_id):
|
|
589
936
|
"""Requires course instance ID"""
|
|
590
|
-
|
|
591
|
-
f"/resultat/internal/kurstillfalle/kursinstans/{course_instance_id}"
|
|
937
|
+
data = self.get_query(
|
|
938
|
+
f"/resultat/internal/kurstillfalle/kursinstans/{course_instance_id}"
|
|
939
|
+
)
|
|
592
940
|
|
|
593
|
-
|
|
594
|
-
return
|
|
595
|
-
|
|
596
|
-
|
|
941
|
+
try:
|
|
942
|
+
return data["Utbildningstillfalle"]
|
|
943
|
+
except KeyError as err:
|
|
944
|
+
err.add_note(f"Response data: {data}")
|
|
945
|
+
raise LadokAPIError(f"Unexpected response format when fetching course rounds for "
|
|
946
|
+
f"instance {course_instance_id}: missing 'Utbildningstillfalle' key") from err
|
|
597
947
|
@
|
|
598
948
|
|
|
599
949
|
We test this method as follows.
|
|
@@ -621,14 +971,19 @@ It requires the course instance ID.
|
|
|
621
971
|
(This is a slightly rewritten version of Maguire's original method.)
|
|
622
972
|
<<LadokSession data methods>>=
|
|
623
973
|
def course_instance_JSON(self, instance_id):
|
|
624
|
-
"""
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
974
|
+
"""
|
|
975
|
+
Returns course instance data for a course with instance ID instance_id
|
|
976
|
+
"""
|
|
977
|
+
try:
|
|
978
|
+
return self.get_query(
|
|
979
|
+
f"/resultat/internal/utbildningsinstans/kursinstans/{instance_id}"
|
|
980
|
+
)
|
|
981
|
+
except LadokAPIError as err:
|
|
982
|
+
raise LadokAPIError(f"Failed to get course instance data for "
|
|
983
|
+
f"{instance_id}: {err}") from err
|
|
984
|
+
except LadokServerError as err:
|
|
985
|
+
raise LadokServerError(f"LADOK server error when getting course instance data for "
|
|
986
|
+
f"{instance_id}: {err}") from err
|
|
632
987
|
@
|
|
633
988
|
|
|
634
989
|
We add the following test.
|
|
@@ -649,6 +1004,136 @@ print(r"\end{minted}")
|
|
|
649
1004
|
\end{pycode}
|
|
650
1005
|
|
|
651
1006
|
|
|
1007
|
+
\section{[[course_instances_JSON]]}
|
|
1008
|
+
|
|
1009
|
+
We can get a list of course instances for a given course code.
|
|
1010
|
+
<<LadokSession data methods>>=
|
|
1011
|
+
# added by GQMJr
|
|
1012
|
+
def course_instances_JSON(self, course_code, lang = 'sv'):
|
|
1013
|
+
"""
|
|
1014
|
+
Returns a list of dictionaries with course instances for a given course code.
|
|
1015
|
+
The course code is a string such as "DD1310". The language code is 'en' or
|
|
1016
|
+
'sv'.
|
|
1017
|
+
|
|
1018
|
+
Note that there seems to be a limit of 403 for the number of pages.
|
|
1019
|
+
"""
|
|
1020
|
+
r = self.session.get(
|
|
1021
|
+
url=self.base_gui_proxy_url + '/resultat/internal/kurstillfalle/filtrera?kurskod=' +
|
|
1022
|
+
course_code + '&page=1&limit=100&skipCount=false&sprakkod=' + lang, # not sure about this one /CO
|
|
1023
|
+
headers=self.headers)
|
|
1024
|
+
if r.status_code == requests.codes.ok:
|
|
1025
|
+
return r.json()
|
|
1026
|
+
raise LadokAPIError(f"failed to get course instances for {course_code}: {r.text}")
|
|
1027
|
+
@
|
|
1028
|
+
|
|
1029
|
+
Let's add a test.
|
|
1030
|
+
This should return a list of dictionaries.
|
|
1031
|
+
<<test functions>>=
|
|
1032
|
+
def test_course_instances_JSON():
|
|
1033
|
+
r = ladok.course_instances_JSON('DD2395')
|
|
1034
|
+
assert type(r) == dict
|
|
1035
|
+
@
|
|
1036
|
+
|
|
1037
|
+
The output looks like this:
|
|
1038
|
+
\begin{pycode}[apitest]
|
|
1039
|
+
print(r"\begin{minted}{JSON}")
|
|
1040
|
+
print(json.dumps(ladok3.clean_data(ladok.course_instances_JSON('DD2395')),
|
|
1041
|
+
indent=2))
|
|
1042
|
+
print(r"\end{minted}")
|
|
1043
|
+
\end{pycode}
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
\section{[[instance_info]]}
|
|
1047
|
+
|
|
1048
|
+
This function gets a course instance by the combination of course code (\eg
|
|
1049
|
+
DD1310) and the five digit round code (\eg 50429).
|
|
1050
|
+
<<LadokSession data methods>>=
|
|
1051
|
+
# added by GQMJr
|
|
1052
|
+
def instance_info(self, course_code, instance_code, lang = 'sv'):
|
|
1053
|
+
"""
|
|
1054
|
+
Returns a dictionary of course instance information.
|
|
1055
|
+
|
|
1056
|
+
course_code - course code, such as "DD1310"
|
|
1057
|
+
|
|
1058
|
+
instance_code - instance of the course ('TillfallesKod')
|
|
1059
|
+
|
|
1060
|
+
lang - language code 'en' or 'sv', defaults to 'sv'
|
|
1061
|
+
"""
|
|
1062
|
+
r = self.session.get(
|
|
1063
|
+
url=self.base_gui_proxy_url +
|
|
1064
|
+
'/resultat/internal/kurstillfalle/filtrera?kurskod=' + course_code +
|
|
1065
|
+
'&page=1&limit=25&skipCount=false&sprakkod=' + lang,
|
|
1066
|
+
headers=self.headers)
|
|
1067
|
+
if r.status_code == requests.codes.ok:
|
|
1068
|
+
rj=r.json()
|
|
1069
|
+
for course in rj['Resultat']:
|
|
1070
|
+
if course['TillfallesKod'] == instance_code:
|
|
1071
|
+
return course
|
|
1072
|
+
# If we get here, the course instance was not found
|
|
1073
|
+
raise LadokNotFoundError(f"course instance {instance_code} not found for course {course_code}")
|
|
1074
|
+
raise LadokAPIError(f"failed to search for course instance {course_code}/{instance_code}: {r.text}")
|
|
1075
|
+
@
|
|
1076
|
+
|
|
1077
|
+
Let's add a test.
|
|
1078
|
+
This should return a dictionary.
|
|
1079
|
+
<<test functions>>=
|
|
1080
|
+
def test_instance_info():
|
|
1081
|
+
r = ladok.instance_info('DD1310', '50429')
|
|
1082
|
+
assert type(r) == dict
|
|
1083
|
+
@
|
|
1084
|
+
|
|
1085
|
+
The output looks like this:
|
|
1086
|
+
\begin{pycode}[apitest]
|
|
1087
|
+
print(r"\begin{minted}{JSON}")
|
|
1088
|
+
print(json.dumps(ladok3.clean_data(ladok.instance_info('DD1310', '50429')),
|
|
1089
|
+
indent=2))
|
|
1090
|
+
print(r"\end{minted}")
|
|
1091
|
+
\end{pycode}
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
\section{[[instance_info_uid]]}
|
|
1095
|
+
|
|
1096
|
+
This function returns the same as above ([[instance_info]]) but uses the
|
|
1097
|
+
course's LADOK ID.
|
|
1098
|
+
This ID can be found as the [[sis_course_id]] in Canvas.
|
|
1099
|
+
<<LadokSession data methods>>=
|
|
1100
|
+
# added by GQMJr
|
|
1101
|
+
def instance_info_uid(self, instance_uid):
|
|
1102
|
+
"""
|
|
1103
|
+
Returns a dictionary of course instance information.
|
|
1104
|
+
|
|
1105
|
+
instance_uid: course's Uid (from course_integration_id or
|
|
1106
|
+
sis_course_id in Canvas)
|
|
1107
|
+
"""
|
|
1108
|
+
r = self.session.get(
|
|
1109
|
+
url=self.base_gui_proxy_url +
|
|
1110
|
+
'/resultat/internal/kurstillfalle/'+instance_uid,
|
|
1111
|
+
headers=self.headers)
|
|
1112
|
+
if r.status_code == requests.codes.ok:
|
|
1113
|
+
return r.json()
|
|
1114
|
+
raise LadokAPIError(f"failed to get course instance info for {instance_uid}: {r.text}")
|
|
1115
|
+
@
|
|
1116
|
+
|
|
1117
|
+
Let's add a test.
|
|
1118
|
+
This should return a dictionary.
|
|
1119
|
+
<<test functions>>=
|
|
1120
|
+
def test_instance_info_uid():
|
|
1121
|
+
r = ladok.instance_info_uid(dasak_round_id)
|
|
1122
|
+
assert type(r) == dict
|
|
1123
|
+
@
|
|
1124
|
+
|
|
1125
|
+
The output looks like this:
|
|
1126
|
+
\begin{pycode}[apitest]
|
|
1127
|
+
print(r"\begin{minted}{JSON}")
|
|
1128
|
+
print(json.dumps(ladok3.clean_data(
|
|
1129
|
+
ladok.instance_info_uid(dasak_round_id)),
|
|
1130
|
+
indent=2))
|
|
1131
|
+
print(r"\end{minted}")
|
|
1132
|
+
\end{pycode}
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
|
|
652
1137
|
\section{Course components}
|
|
653
1138
|
|
|
654
1139
|
There are two ways to get the components for a course.
|
|
@@ -661,14 +1146,29 @@ This one includes data such as the number of registered students as well,
|
|
|
661
1146
|
unlike the method in the next section.
|
|
662
1147
|
<<LadokSession data methods>>=
|
|
663
1148
|
def course_round_components_JSON(self, round_id):
|
|
664
|
-
|
|
1149
|
+
"""
|
|
1150
|
+
Fetch course components for a given course round.
|
|
1151
|
+
|
|
1152
|
+
Args:
|
|
1153
|
+
round_id (str): The unique identifier of the course round.
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
list: A list of course component JSON objects.
|
|
1157
|
+
|
|
1158
|
+
Raises:
|
|
1159
|
+
LadokServerError: If the request fails or server returns an error message.
|
|
1160
|
+
"""
|
|
1161
|
+
data = self.put_query(
|
|
665
1162
|
"/resultat/internal/kurstillfalle/moment",
|
|
666
1163
|
{"Identitet": [round_id]}
|
|
667
1164
|
)
|
|
668
1165
|
|
|
669
|
-
|
|
670
|
-
return
|
|
671
|
-
|
|
1166
|
+
try:
|
|
1167
|
+
return data["MomentPerKurstillfallen"][0]["Moment"]
|
|
1168
|
+
except (KeyError, IndexError) as err:
|
|
1169
|
+
err.add_note(f"Response data: {data}")
|
|
1170
|
+
raise LadokAPIError(f"Unexpected response format when fetching components for "
|
|
1171
|
+
f"round {round_id}: {type(err).__name__}") from err
|
|
672
1172
|
@
|
|
673
1173
|
|
|
674
1174
|
We add the following test.
|
|
@@ -698,14 +1198,29 @@ This method fetches the course components for a course instance, \ie a version
|
|
|
698
1198
|
of the syllabus.
|
|
699
1199
|
<<LadokSession data methods>>=
|
|
700
1200
|
def course_instance_components_JSON(self, course_instance_id):
|
|
701
|
-
|
|
1201
|
+
"""
|
|
1202
|
+
Fetch course components for a given course instance.
|
|
1203
|
+
|
|
1204
|
+
Args:
|
|
1205
|
+
course_instance_id (str): The unique identifier of the course instance.
|
|
1206
|
+
|
|
1207
|
+
Returns:
|
|
1208
|
+
dict: JSON object containing course instance components.
|
|
1209
|
+
|
|
1210
|
+
Raises:
|
|
1211
|
+
LadokServerError: If the request fails or server returns an error message.
|
|
1212
|
+
"""
|
|
1213
|
+
data = self.put_query(
|
|
702
1214
|
"/resultat/internal/utbildningsinstans/moduler",
|
|
703
1215
|
{"Identitet": [course_instance_id]}
|
|
704
1216
|
)
|
|
705
1217
|
|
|
706
|
-
|
|
707
|
-
return
|
|
708
|
-
|
|
1218
|
+
try:
|
|
1219
|
+
return data["Utbildningsinstans"][0]
|
|
1220
|
+
except (KeyError, IndexError) as err:
|
|
1221
|
+
err.add_note(f"Response data: {data}")
|
|
1222
|
+
raise LadokAPIError(f"Unexpected response format when fetching components for "
|
|
1223
|
+
f"instance {course_instance_id}: {type(err).__name__}") from err
|
|
709
1224
|
@
|
|
710
1225
|
|
|
711
1226
|
We add the following test code.
|
|
@@ -762,16 +1277,18 @@ def search_reported_results_JSON(self, course_round_id, component_instance_id):
|
|
|
762
1277
|
"StudenterUID": []
|
|
763
1278
|
}
|
|
764
1279
|
|
|
765
|
-
|
|
1280
|
+
data = self.put_query(
|
|
766
1281
|
"/resultat/internal/studieresultat/rapportera"
|
|
767
1282
|
f"/utbildningsinstans/{component_instance_id}/sok",
|
|
768
1283
|
put_data)
|
|
769
1284
|
|
|
770
|
-
|
|
771
|
-
return
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1285
|
+
try:
|
|
1286
|
+
return data["Resultat"]
|
|
1287
|
+
except KeyError as err:
|
|
1288
|
+
err.add_note(f"Response data: {data}")
|
|
1289
|
+
raise LadokAPIError(f"Unexpected response format when searching for results for "
|
|
1290
|
+
f"round {course_round_id}, component {component_instance_id}: "
|
|
1291
|
+
f"missing 'Resultat' key") from err
|
|
775
1292
|
@
|
|
776
1293
|
|
|
777
1294
|
We write the following test.
|
|
@@ -800,6 +1317,19 @@ print(r"\end{minted}")
|
|
|
800
1317
|
Another method, which gives slightly different results is the following.
|
|
801
1318
|
<<LadokSession data methods>>=
|
|
802
1319
|
def search_course_results_JSON(self, course_round_id, component_instance_id):
|
|
1320
|
+
"""
|
|
1321
|
+
Retrieve course results for a specific component in a course round.
|
|
1322
|
+
|
|
1323
|
+
Args:
|
|
1324
|
+
course_round_id (str): The unique identifier of the course round.
|
|
1325
|
+
component_instance_id (str): The unique identifier of the component instance.
|
|
1326
|
+
|
|
1327
|
+
Returns:
|
|
1328
|
+
list: List of course result JSON objects.
|
|
1329
|
+
|
|
1330
|
+
Raises:
|
|
1331
|
+
LadokAPIError: If the request fails.
|
|
1332
|
+
"""
|
|
803
1333
|
put_data = {
|
|
804
1334
|
"KurstillfallenUID": [course_round_id],
|
|
805
1335
|
"Tillstand": ["REGISTRERAD", "AVKLARAD", "AVBROTT"],
|
|
@@ -808,15 +1338,17 @@ def search_course_results_JSON(self, course_round_id, component_instance_id):
|
|
|
808
1338
|
"Page": 1,
|
|
809
1339
|
}
|
|
810
1340
|
|
|
811
|
-
|
|
1341
|
+
data = self.put_query(
|
|
812
1342
|
"/resultat/internal/resultatuppfoljning/resultatuppfoljning/sok",
|
|
813
1343
|
put_data)
|
|
814
1344
|
|
|
815
|
-
|
|
816
|
-
return
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1345
|
+
try:
|
|
1346
|
+
return data["Resultat"]
|
|
1347
|
+
except KeyError as err:
|
|
1348
|
+
err.add_note(f"Response data: {data}")
|
|
1349
|
+
raise LadokAPIError(f"Unexpected response format when searching course results for "
|
|
1350
|
+
f"round {course_round_id}, component {component_instance_id}: "
|
|
1351
|
+
f"missing 'Resultat' key") from err
|
|
820
1352
|
@
|
|
821
1353
|
|
|
822
1354
|
We test this by the following.
|
|
@@ -847,14 +1379,18 @@ LADOK changed this API request in 2022.
|
|
|
847
1379
|
<<LadokSession data methods>>=
|
|
848
1380
|
def student_results_JSON(self, student_id, course_education_id):
|
|
849
1381
|
"""Returns the results for a student on a course"""
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1382
|
+
try:
|
|
1383
|
+
return self.get_query(
|
|
1384
|
+
"/resultat/internal/studentenskurser/kursinformation"
|
|
1385
|
+
f"/student/{student_id}/kursUID/{course_education_id}"
|
|
1386
|
+
)
|
|
1387
|
+
except LadokAPIError as err:
|
|
1388
|
+
raise LadokAPIError(f"Failed to get results for student {student_id} "
|
|
1389
|
+
f"on course {course_education_id}: {err}") from err
|
|
1390
|
+
except LadokServerError as err:
|
|
1391
|
+
raise LadokServerError(f"LADOK server error when getting results for "
|
|
1392
|
+
f"student {student_id} on course {course_education_id}: "
|
|
1393
|
+
f"{err}") from err
|
|
858
1394
|
@
|
|
859
1395
|
|
|
860
1396
|
We test this in the following way.
|
|
@@ -897,23 +1433,28 @@ def create_result_JSON(self,
|
|
|
897
1433
|
student_id, course_instance_id, component_id,
|
|
898
1434
|
grade_id, date,
|
|
899
1435
|
project_title=None):
|
|
900
|
-
"""
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
"
|
|
908
|
-
"
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1436
|
+
"""
|
|
1437
|
+
Creates a new result
|
|
1438
|
+
"""
|
|
1439
|
+
try:
|
|
1440
|
+
return self.post_query(
|
|
1441
|
+
f"/resultat/internal/resultat/student/{student_id}"
|
|
1442
|
+
f"/kursinstans/{course_instance_id}"
|
|
1443
|
+
f"/utbildningsinstans/{component_id}"
|
|
1444
|
+
f"/skapa",
|
|
1445
|
+
{
|
|
1446
|
+
"Betygsgrad": grade_id,
|
|
1447
|
+
"Examinationsdatum": date,
|
|
1448
|
+
"Projekttitel": project_title
|
|
1449
|
+
}
|
|
1450
|
+
)
|
|
1451
|
+
except LadokAPIError as err:
|
|
1452
|
+
raise LadokAPIError(f"Failed to create result for student {student_id} "
|
|
1453
|
+
f"on component {component_id}: {err}") from err
|
|
1454
|
+
except LadokServerError as err:
|
|
1455
|
+
raise LadokServerError(f"LADOK server error when creating result for "
|
|
1456
|
+
f"student {student_id} on component {component_id}: "
|
|
1457
|
+
f"{err}") from err
|
|
917
1458
|
@
|
|
918
1459
|
|
|
919
1460
|
We test this in the following way.
|
|
@@ -965,20 +1506,38 @@ we did for [[create_result_JSON]].
|
|
|
965
1506
|
<<LadokSession data methods>>=
|
|
966
1507
|
def update_result_JSON(self,
|
|
967
1508
|
result_id, grade_id, date, last_modified, notes=[]):
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1509
|
+
"""Update an existing result draft in LADOK.
|
|
1510
|
+
|
|
1511
|
+
Note: Cannot be used to update finalized results. Uses ResultatUID, not StudieresultatUID.
|
|
1512
|
+
|
|
1513
|
+
Args:
|
|
1514
|
+
result_id (str): The unique result identifier (ResultatUID).
|
|
1515
|
+
grade_id (int): The numeric grade identifier from the grade scale.
|
|
1516
|
+
date (str): Examination date in YYYY-MM-DD format.
|
|
1517
|
+
last_modified (str): Last modification timestamp to prevent conflicts.
|
|
1518
|
+
notes (list, optional): List of notes to attach to the result.
|
|
1519
|
+
|
|
1520
|
+
Returns:
|
|
1521
|
+
dict: Updated result data from LADOK.
|
|
1522
|
+
|
|
1523
|
+
Raises:
|
|
1524
|
+
Exception: If the update request fails or is rejected by LADOK.
|
|
1525
|
+
"""
|
|
1526
|
+
try:
|
|
1527
|
+
return self.put_query(
|
|
1528
|
+
f"/resultat/internal/resultat/uppdatera/{result_id}",
|
|
1529
|
+
{
|
|
1530
|
+
'Betygsgrad': grade_id,
|
|
1531
|
+
'Examinationsdatum': date,
|
|
1532
|
+
'Noteringar': notes,
|
|
1533
|
+
'SenasteResultatandring': last_modified
|
|
1534
|
+
}
|
|
1535
|
+
)
|
|
1536
|
+
except LadokAPIError as err:
|
|
1537
|
+
raise LadokAPIError(f"Failed to update result {result_id}: {err}") from err
|
|
1538
|
+
except LadokServerError as err:
|
|
1539
|
+
raise LadokServerError(f"LADOK server error when updating result "
|
|
1540
|
+
f"{result_id}: {err}") from err
|
|
982
1541
|
@
|
|
983
1542
|
|
|
984
1543
|
We test this in the following way.
|
|
@@ -1022,14 +1581,17 @@ We start with who can attest.
|
|
|
1022
1581
|
<<LadokSession data methods>>=
|
|
1023
1582
|
def result_attestants_JSON(self, result_id):
|
|
1024
1583
|
"""Returns a list of result attestants"""
|
|
1025
|
-
|
|
1584
|
+
data = self.put_query(
|
|
1026
1585
|
"/resultat/internal/anvandare/resultatrattighet/attestanter/kurstillfallesrapportering",
|
|
1027
1586
|
{"Identitet": [result_id]}
|
|
1028
1587
|
)
|
|
1029
1588
|
|
|
1030
|
-
|
|
1031
|
-
return
|
|
1032
|
-
|
|
1589
|
+
try:
|
|
1590
|
+
return data["Anvandare"]
|
|
1591
|
+
except KeyError as err:
|
|
1592
|
+
err.add_note(f"Response data: {data}")
|
|
1593
|
+
raise LadokAPIError(f"Unexpected response format when fetching attestants for "
|
|
1594
|
+
f"result {result_id}: missing 'Anvandare' key") from err
|
|
1033
1595
|
@ The [[result_id]] is the ID returned in the [[ResultatUID]] field in the
|
|
1034
1596
|
response from the [[create_result_JSON]] method.
|
|
1035
1597
|
|
|
@@ -1061,15 +1623,18 @@ organization).
|
|
|
1061
1623
|
<<LadokSession data methods>>=
|
|
1062
1624
|
def result_reporters_JSON(self, organization_id):
|
|
1063
1625
|
"""Returns a list of who can report results in an organization"""
|
|
1064
|
-
|
|
1626
|
+
data = self.get_query(
|
|
1065
1627
|
"/kataloginformation/internal/anvandare/organisation/" +
|
|
1066
1628
|
organization_id + "/resultatrapportorer",
|
|
1067
1629
|
"application/vnd.ladok-kataloginformation+json"
|
|
1068
1630
|
)
|
|
1069
1631
|
|
|
1070
|
-
|
|
1071
|
-
return
|
|
1072
|
-
|
|
1632
|
+
try:
|
|
1633
|
+
return data["Anvandare"]
|
|
1634
|
+
except KeyError as err:
|
|
1635
|
+
err.add_note(f"Response data: {data}")
|
|
1636
|
+
raise LadokAPIError(f"Unexpected response format when fetching reporters for "
|
|
1637
|
+
f"organization {organization_id}: missing 'Anvandare' key") from err
|
|
1073
1638
|
@
|
|
1074
1639
|
|
|
1075
1640
|
We test this method as follows.
|
|
@@ -1096,14 +1661,25 @@ Usually, we want to set the reporter to the logged-in user.
|
|
|
1096
1661
|
We can use the following API call to get information about the logged-in user.
|
|
1097
1662
|
<<LadokSession data methods>>=
|
|
1098
1663
|
def user_info_JSON(self):
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1664
|
+
"""
|
|
1665
|
+
Get information about the currently logged-in user.
|
|
1666
|
+
|
|
1667
|
+
Returns:
|
|
1668
|
+
dict: User information including name, roles, and permissions.
|
|
1669
|
+
|
|
1670
|
+
Raises:
|
|
1671
|
+
Exception: If the request fails or returns an error status.
|
|
1672
|
+
"""
|
|
1673
|
+
try:
|
|
1674
|
+
return self.get_query(
|
|
1675
|
+
"/kataloginformation/internal/anvandare/anvandarinformation",
|
|
1676
|
+
"application/vnd.ladok-kataloginformation+json"
|
|
1677
|
+
)
|
|
1678
|
+
except LadokAPIError as err:
|
|
1679
|
+
raise LadokAPIError(f"Failed to get user info: {err}") from err
|
|
1680
|
+
except LadokServerError as err:
|
|
1681
|
+
raise LadokServerError(f"LADOK server error when getting user info: "
|
|
1682
|
+
f"{err}") from err
|
|
1107
1683
|
@
|
|
1108
1684
|
|
|
1109
1685
|
We test this as follows.
|
|
@@ -1134,20 +1710,22 @@ def finalize_result_JSON(self,
|
|
|
1134
1710
|
result_id, last_modified, reporter_id, attestant_ids=[],
|
|
1135
1711
|
others=[]):
|
|
1136
1712
|
"""Marks a result as finalized (klarmarkera)"""
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1713
|
+
try:
|
|
1714
|
+
return self.put_query(
|
|
1715
|
+
f"/resultat/internal/resultat/klarmarkera/{result_id}",
|
|
1716
|
+
{
|
|
1717
|
+
"Beslutsfattare": attestant_ids,
|
|
1718
|
+
"KlarmarkeradAvUID": reporter_id,
|
|
1719
|
+
"RattadAv": [],
|
|
1720
|
+
"OvrigaMedverkande": "\n".join(set(others)),
|
|
1721
|
+
"ResultatetsSenastSparad": last_modified
|
|
1722
|
+
}
|
|
1723
|
+
)
|
|
1724
|
+
except LadokAPIError as err:
|
|
1725
|
+
raise LadokAPIError(f"Failed to finalize result {result_id}: {err}") from err
|
|
1726
|
+
except LadokServerError as err:
|
|
1727
|
+
raise LadokServerError(f"LADOK server error when finalizing result "
|
|
1728
|
+
f"{result_id}: {err}") from err
|
|
1151
1729
|
@ This method returns a copy of the finalized result.
|
|
1152
1730
|
|
|
1153
1731
|
We test this in the following way.
|
|
@@ -1187,20 +1765,22 @@ uses [[uppdateraklarmarkerat]] instead of just [[uppdatera]].
|
|
|
1187
1765
|
<<LadokSession data methods>>=
|
|
1188
1766
|
def update_finalized_result_JSON(self,
|
|
1189
1767
|
result_id, grade_id, date, last_modified, notes=[]):
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1768
|
+
try:
|
|
1769
|
+
return self.put_query(
|
|
1770
|
+
f"/resultat/internal/resultat/uppdateraklarmarkerat/{result_id}",
|
|
1771
|
+
{
|
|
1772
|
+
'Betygsgrad': grade_id,
|
|
1773
|
+
'Examinationsdatum': date,
|
|
1774
|
+
'Noteringar': notes,
|
|
1775
|
+
'SenasteResultatandring': last_modified
|
|
1776
|
+
}
|
|
1777
|
+
)
|
|
1778
|
+
except LadokAPIError as err:
|
|
1779
|
+
raise LadokAPIError(f"Failed to update finalized result {result_id}: "
|
|
1780
|
+
f"{err}") from err
|
|
1781
|
+
except LadokServerError as err:
|
|
1782
|
+
raise LadokServerError(f"LADOK server error when updating finalized result "
|
|
1783
|
+
f"{result_id}: {err}") from err
|
|
1204
1784
|
@
|
|
1205
1785
|
|
|
1206
1786
|
We test this in the following way.
|
|
@@ -1233,18 +1813,20 @@ print(r"\end{minted}")
|
|
|
1233
1813
|
We can also change a result from finalized back to draft status.
|
|
1234
1814
|
<<LadokSession data methods>>=
|
|
1235
1815
|
def finalized_result_to_draft_JSON(self, result_id, last_modified):
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1816
|
+
try:
|
|
1817
|
+
return self.put_query(
|
|
1818
|
+
f"/resultat/internal/resultat/tillbakatillutkast/{result_id}",
|
|
1819
|
+
{
|
|
1820
|
+
'ResultatUID': result_id,
|
|
1821
|
+
'ResultatetsSenastSparad': last_modified
|
|
1822
|
+
}
|
|
1823
|
+
)
|
|
1824
|
+
except LadokAPIError as err:
|
|
1825
|
+
raise LadokAPIError(f"Failed to change finalized result {result_id} to draft: "
|
|
1826
|
+
f"{err}") from err
|
|
1827
|
+
except LadokServerError as err:
|
|
1828
|
+
raise LadokServerError(f"LADOK server error when changing finalized result "
|
|
1829
|
+
f"{result_id} to draft: {err}") from err
|
|
1248
1830
|
@
|
|
1249
1831
|
|
|
1250
1832
|
We test this in the following way.
|
|
@@ -1275,13 +1857,17 @@ print(r"\end{minted}")
|
|
|
1275
1857
|
We can also change a result from finalized back to draft status.
|
|
1276
1858
|
<<LadokSession data methods>>=
|
|
1277
1859
|
def remove_result_draft_JSON(self, result_id):
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
raise
|
|
1284
|
-
|
|
1860
|
+
try:
|
|
1861
|
+
data = self.del_query(
|
|
1862
|
+
f"/resultat/internal/resultat/tabort/{result_id}"
|
|
1863
|
+
)
|
|
1864
|
+
except LadokAPIError as err:
|
|
1865
|
+
raise LadokAPIError(f"LADOK request to remove draft result "
|
|
1866
|
+
f"{result_id} failed: {err}") from err
|
|
1867
|
+
except LadokServerError as err:
|
|
1868
|
+
raise LadokServerError(f"LADOK server error when removing "
|
|
1869
|
+
f"draft result {result_id}: "
|
|
1870
|
+
f"{err}") from err
|
|
1285
1871
|
@
|
|
1286
1872
|
|
|
1287
1873
|
We test this in the following way.
|
|
@@ -1344,11 +1930,12 @@ def participants_JSON(self, course_round_id, /, **kwargs):
|
|
|
1344
1930
|
'/studiedeltagande/internal/deltagare/kurstillfalle',
|
|
1345
1931
|
put_data,
|
|
1346
1932
|
"application/vnd.ladok-studiedeltagande+json")
|
|
1347
|
-
|
|
1348
|
-
return response
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1933
|
+
try:
|
|
1934
|
+
return response["Resultat"]
|
|
1935
|
+
except KeyError as err:
|
|
1936
|
+
err.add_note(f"Response data: {response}")
|
|
1937
|
+
raise LadokAPIError(f"Unexpected response format when fetching participants for "
|
|
1938
|
+
f"round {course_round_id}: missing 'Resultat' key") from err
|
|
1352
1939
|
@
|
|
1353
1940
|
|
|
1354
1941
|
We test this as follows.
|
|
@@ -1371,3 +1958,841 @@ print(json.dumps(results, indent=2, ensure_ascii=False))
|
|
|
1371
1958
|
print(r"\end{minted}")
|
|
1372
1959
|
\end{pycode}
|
|
1373
1960
|
|
|
1961
|
+
|
|
1962
|
+
\chapter{Other API calls}
|
|
1963
|
+
|
|
1964
|
+
The following API calls were explored and written by Chip Maguire.
|
|
1965
|
+
Bosk has merely added tests and generated example output, as well as minor
|
|
1966
|
+
reformatting of the code.
|
|
1967
|
+
|
|
1968
|
+
\section{[[grading_rights]]}
|
|
1969
|
+
|
|
1970
|
+
We can get a list of our rights in LADOK.
|
|
1971
|
+
<<LadokSession data methods>>=
|
|
1972
|
+
# added by GQMJr
|
|
1973
|
+
def grading_rights(self):
|
|
1974
|
+
"""
|
|
1975
|
+
Returns a list of dictionaries with the grading rights of the logged in user.
|
|
1976
|
+
"""
|
|
1977
|
+
r = self.session.get(
|
|
1978
|
+
url=self.base_gui_proxy_url +
|
|
1979
|
+
'/resultat/internal/resultatrattighet/listaforinloggadanvandare',
|
|
1980
|
+
headers=self.headers)
|
|
1981
|
+
if r.status_code == requests.codes.ok:
|
|
1982
|
+
return r.json()['Resultatrattighet']
|
|
1983
|
+
raise LadokAPIError(f"failed to get grading rights: {r.text}")
|
|
1984
|
+
@
|
|
1985
|
+
|
|
1986
|
+
Let's add a test.
|
|
1987
|
+
This should return a list of dictionaries.
|
|
1988
|
+
<<test functions>>=
|
|
1989
|
+
def test_grading_rights():
|
|
1990
|
+
r = ladok.grading_rights()
|
|
1991
|
+
assert type(r) == list
|
|
1992
|
+
assert type(r[0]) == dict
|
|
1993
|
+
@
|
|
1994
|
+
|
|
1995
|
+
The output looks like this:
|
|
1996
|
+
\begin{pycode}[apitest]
|
|
1997
|
+
print(r"\begin{minted}{JSON}")
|
|
1998
|
+
print(json.dumps(ladok3.clean_data(ladok.grading_rights()), indent=2))
|
|
1999
|
+
print(r"\end{minted}")
|
|
2000
|
+
\end{pycode}
|
|
2001
|
+
|
|
2002
|
+
|
|
2003
|
+
\section{[[organization_info_JSON]]}
|
|
2004
|
+
|
|
2005
|
+
We can get information about the organization.
|
|
2006
|
+
<<LadokSession data methods>>=
|
|
2007
|
+
# added by GQMJr
|
|
2008
|
+
def organization_info_JSON(self):
|
|
2009
|
+
"""
|
|
2010
|
+
Returns a dictionary of organization information for the entire institution
|
|
2011
|
+
of the logged in user.
|
|
2012
|
+
"""
|
|
2013
|
+
r = self.session.get(
|
|
2014
|
+
url=self.base_gui_proxy_url + '/resultat/internal/organisation/utanlankar',
|
|
2015
|
+
headers=self.headers)
|
|
2016
|
+
if r.status_code == requests.codes.ok:
|
|
2017
|
+
return r.json()
|
|
2018
|
+
raise LadokAPIError(f"failed to get organization info: {r.text}")
|
|
2019
|
+
@
|
|
2020
|
+
|
|
2021
|
+
Let's add a test.
|
|
2022
|
+
This should return a dictionary.
|
|
2023
|
+
<<test functions>>=
|
|
2024
|
+
def test_organization_info_JSON():
|
|
2025
|
+
r = ladok.organization_info_JSON()
|
|
2026
|
+
assert type(r) == dict
|
|
2027
|
+
@
|
|
2028
|
+
|
|
2029
|
+
The output looks like this:
|
|
2030
|
+
\begin{pycode}[apitest]
|
|
2031
|
+
print(r"\begin{minted}{JSON}")
|
|
2032
|
+
print(json.dumps(ladok3.clean_data(ladok.organization_info_JSON()), indent=2))
|
|
2033
|
+
print(r"\end{minted}")
|
|
2034
|
+
\end{pycode}
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
\section{[[larosatesinformation_JSON]]}
|
|
2038
|
+
|
|
2039
|
+
<<LadokSession data methods>>=
|
|
2040
|
+
# added by GQMJr
|
|
2041
|
+
def larosatesinformation_JSON(self):
|
|
2042
|
+
"""
|
|
2043
|
+
Returns a dictionary of the university or college information.
|
|
2044
|
+
"""
|
|
2045
|
+
r = self.session.get(
|
|
2046
|
+
url=self.base_gui_proxy_url +
|
|
2047
|
+
'/kataloginformation/internal/grunddata/larosatesinformation',
|
|
2048
|
+
headers=self.headers).json()
|
|
2049
|
+
return r
|
|
2050
|
+
@
|
|
2051
|
+
|
|
2052
|
+
Let's add a test.
|
|
2053
|
+
This should return a dictionary.
|
|
2054
|
+
<<test functions>>=
|
|
2055
|
+
def test_larosatesinformation_JSON():
|
|
2056
|
+
r = ladok.larosatesinformation_JSON()
|
|
2057
|
+
assert type(r) == dict
|
|
2058
|
+
@
|
|
2059
|
+
|
|
2060
|
+
The output looks like this:
|
|
2061
|
+
\begin{pycode}[apitest]
|
|
2062
|
+
print(r"\begin{minted}{JSON}")
|
|
2063
|
+
print(json.dumps(ladok3.clean_data(ladok.larosatesinformation_JSON()),
|
|
2064
|
+
indent=2))
|
|
2065
|
+
print(r"\end{minted}")
|
|
2066
|
+
\end{pycode}
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
\section{[[undervisningssprak_JSON]]}
|
|
2070
|
+
|
|
2071
|
+
<<LadokSession data methods>>=
|
|
2072
|
+
# added by GQMJr
|
|
2073
|
+
def undervisningssprak_JSON(self):
|
|
2074
|
+
"""
|
|
2075
|
+
Returns a dictionary of teaching languages.
|
|
2076
|
+
"""
|
|
2077
|
+
r = self.session.get(
|
|
2078
|
+
url=self.base_gui_proxy_url +
|
|
2079
|
+
'/kataloginformation/internal/grunddata/undervisningssprak',
|
|
2080
|
+
headers=self.headers).json()
|
|
2081
|
+
return r
|
|
2082
|
+
@
|
|
2083
|
+
|
|
2084
|
+
Let's add a test.
|
|
2085
|
+
This should return a dictionary.
|
|
2086
|
+
<<test functions>>=
|
|
2087
|
+
def test_undervisningssprak_JSON():
|
|
2088
|
+
r = ladok.undervisningssprak_JSON()
|
|
2089
|
+
assert type(r) == dict
|
|
2090
|
+
@
|
|
2091
|
+
|
|
2092
|
+
The output looks like this:
|
|
2093
|
+
\begin{pycode}[apitest]
|
|
2094
|
+
print(r"\begin{minted}{JSON}")
|
|
2095
|
+
print(json.dumps(ladok3.clean_data(ladok.undervisningssprak_JSON()), indent=2))
|
|
2096
|
+
print(r"\end{minted}")
|
|
2097
|
+
\end{pycode}
|
|
2098
|
+
|
|
2099
|
+
|
|
2100
|
+
\section{[[i18n_translation_JSON]]}
|
|
2101
|
+
|
|
2102
|
+
<<LadokSession data methods>>=
|
|
2103
|
+
# added by GQMJr
|
|
2104
|
+
def i18n_translation_JSON(self, lang = 'sv'):
|
|
2105
|
+
"""
|
|
2106
|
+
Returns a dictionary of i18n translations used in Ladok3.
|
|
2107
|
+
"""
|
|
2108
|
+
r = self.session.get(
|
|
2109
|
+
url=self.base_gui_proxy_url +
|
|
2110
|
+
'/kataloginformation/internal/i18n/oversattningar/sprakkod/' + lang,
|
|
2111
|
+
headers=self.headers).json()
|
|
2112
|
+
return r
|
|
2113
|
+
@
|
|
2114
|
+
|
|
2115
|
+
Let's add a test.
|
|
2116
|
+
This should return a dictionary.
|
|
2117
|
+
<<test functions>>=
|
|
2118
|
+
def test_i18n_translation_JSON():
|
|
2119
|
+
r = ladok.i18n_translation_JSON()
|
|
2120
|
+
assert type(r) == dict
|
|
2121
|
+
@
|
|
2122
|
+
|
|
2123
|
+
The output looks like this:
|
|
2124
|
+
\begin{pycode}[apitest]
|
|
2125
|
+
print(r"\begin{minted}{JSON}")
|
|
2126
|
+
print(json.dumps(ladok3.clean_data(ladok.i18n_translation_JSON()), indent=2))
|
|
2127
|
+
print(r"\end{minted}")
|
|
2128
|
+
\end{pycode}
|
|
2129
|
+
|
|
2130
|
+
|
|
2131
|
+
|
|
2132
|
+
\section{[[svenskorter_JSON]]}
|
|
2133
|
+
|
|
2134
|
+
<<LadokSession data methods>>=
|
|
2135
|
+
# added by GQMJr
|
|
2136
|
+
def svenskorter_JSON(self):
|
|
2137
|
+
"""
|
|
2138
|
+
Returns a dictionary of Swedish places with their KommunID.
|
|
2139
|
+
"""
|
|
2140
|
+
r = self.session.get(
|
|
2141
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/svenskort',
|
|
2142
|
+
headers=self.headers).json()
|
|
2143
|
+
return r
|
|
2144
|
+
@
|
|
2145
|
+
|
|
2146
|
+
Let's add a test.
|
|
2147
|
+
This should return a dictionary.
|
|
2148
|
+
<<test functions>>=
|
|
2149
|
+
def test_svenskorter_JSON():
|
|
2150
|
+
r = ladok.svenskorter_JSON()
|
|
2151
|
+
assert type(r) == dict
|
|
2152
|
+
@
|
|
2153
|
+
|
|
2154
|
+
The output looks like this:
|
|
2155
|
+
\begin{pycode}[apitest]
|
|
2156
|
+
print(r"\begin{minted}{JSON}")
|
|
2157
|
+
print(json.dumps(ladok3.clean_data(ladok.svenskorter_JSON()), indent=2))
|
|
2158
|
+
print(r"\end{minted}")
|
|
2159
|
+
\end{pycode}
|
|
2160
|
+
|
|
2161
|
+
|
|
2162
|
+
|
|
2163
|
+
\section{[[kommuner_JSON]]}
|
|
2164
|
+
|
|
2165
|
+
<<LadokSession data methods>>=
|
|
2166
|
+
# added by GQMJr
|
|
2167
|
+
def kommuner_JSON(self):
|
|
2168
|
+
"""
|
|
2169
|
+
Returns a dictionary of Swedish municipalities.
|
|
2170
|
+
"""
|
|
2171
|
+
r = self.session.get(
|
|
2172
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/kommun',
|
|
2173
|
+
headers=self.headers).json()
|
|
2174
|
+
return r
|
|
2175
|
+
@
|
|
2176
|
+
|
|
2177
|
+
Let's add a test.
|
|
2178
|
+
This should return a dictionary.
|
|
2179
|
+
<<test functions>>=
|
|
2180
|
+
def test_kommuner_JSON():
|
|
2181
|
+
r = ladok.kommuner_JSON()
|
|
2182
|
+
assert type(r) == dict
|
|
2183
|
+
@
|
|
2184
|
+
|
|
2185
|
+
The output looks like this:
|
|
2186
|
+
\begin{pycode}[apitest]
|
|
2187
|
+
print(r"\begin{minted}{JSON}")
|
|
2188
|
+
print(json.dumps(ladok3.clean_data(ladok.kommuner_JSON()), indent=2))
|
|
2189
|
+
print(r"\end{minted}")
|
|
2190
|
+
\end{pycode}
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
\section{[[lander_JSON]]}
|
|
2194
|
+
|
|
2195
|
+
<<LadokSession data methods>>=
|
|
2196
|
+
# added by GQMJr
|
|
2197
|
+
def lander_JSON(self):
|
|
2198
|
+
"""
|
|
2199
|
+
Returns a dictionary of countries.
|
|
2200
|
+
"""
|
|
2201
|
+
r = self.session.get(
|
|
2202
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/land',
|
|
2203
|
+
headers=self.headers).json()
|
|
2204
|
+
return r
|
|
2205
|
+
@
|
|
2206
|
+
|
|
2207
|
+
Let's add a test.
|
|
2208
|
+
This should return a dictionary.
|
|
2209
|
+
<<test functions>>=
|
|
2210
|
+
def test_lander_JSON():
|
|
2211
|
+
r = ladok.lander_JSON()
|
|
2212
|
+
assert type(r) == dict
|
|
2213
|
+
@
|
|
2214
|
+
|
|
2215
|
+
The output looks like this:
|
|
2216
|
+
\begin{pycode}[apitest]
|
|
2217
|
+
print(r"\begin{minted}{JSON}")
|
|
2218
|
+
print(json.dumps(ladok3.clean_data(ladok.lander_JSON()), indent=2))
|
|
2219
|
+
print(r"\end{minted}")
|
|
2220
|
+
\end{pycode}
|
|
2221
|
+
|
|
2222
|
+
|
|
2223
|
+
|
|
2224
|
+
\section{[[undervisningstid_JSON]]}
|
|
2225
|
+
|
|
2226
|
+
<<LadokSession data methods>>=
|
|
2227
|
+
# added by GQMJr
|
|
2228
|
+
def undervisningstid_JSON(self):
|
|
2229
|
+
"""
|
|
2230
|
+
Returns a dictionary of teaching times.
|
|
2231
|
+
"""
|
|
2232
|
+
r = self.session.get(
|
|
2233
|
+
url=self.base_gui_proxy_url +
|
|
2234
|
+
'/kataloginformation/internal/grunddata/undervisningstid',
|
|
2235
|
+
headers=self.headers).json()
|
|
2236
|
+
return r
|
|
2237
|
+
@
|
|
2238
|
+
|
|
2239
|
+
Let's add a test.
|
|
2240
|
+
This should return a dictionary.
|
|
2241
|
+
<<test functions>>=
|
|
2242
|
+
def test_undervisningstid_JSON():
|
|
2243
|
+
r = ladok.undervisningstid_JSON()
|
|
2244
|
+
assert type(r) == dict
|
|
2245
|
+
@
|
|
2246
|
+
|
|
2247
|
+
The output looks like this:
|
|
2248
|
+
\begin{pycode}[apitest]
|
|
2249
|
+
print(r"\begin{minted}{JSON}")
|
|
2250
|
+
print(json.dumps(ladok3.clean_data(ladok.undervisningstid_JSON()), indent=2))
|
|
2251
|
+
print(r"\end{minted}")
|
|
2252
|
+
\end{pycode}
|
|
2253
|
+
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
\section{[[successivfordjupning_JSON]]}
|
|
2257
|
+
|
|
2258
|
+
<<LadokSession data methods>>=
|
|
2259
|
+
# RETURNERAR JSON of Successive Specializations
|
|
2260
|
+
def successivfordjupning_JSON(self):
|
|
2261
|
+
"""
|
|
2262
|
+
Returns a dictionary of Successive Specializations.
|
|
2263
|
+
"""
|
|
2264
|
+
r = self.session.get(
|
|
2265
|
+
url=self.base_gui_proxy_url +
|
|
2266
|
+
'/kataloginformation/internal/grunddata/successivfordjupning',
|
|
2267
|
+
headers=self.headers).json()
|
|
2268
|
+
return r
|
|
2269
|
+
@
|
|
2270
|
+
|
|
2271
|
+
Let's add a test.
|
|
2272
|
+
This should return a dictionary.
|
|
2273
|
+
<<test functions>>=
|
|
2274
|
+
def test_successivfordjupning_JSON():
|
|
2275
|
+
r = ladok.successivfordjupning_JSON()
|
|
2276
|
+
assert type(r) == dict
|
|
2277
|
+
@
|
|
2278
|
+
|
|
2279
|
+
The output looks like this:
|
|
2280
|
+
\begin{pycode}[apitest]
|
|
2281
|
+
print(r"\begin{minted}{JSON}")
|
|
2282
|
+
print(json.dumps(ladok3.clean_data(ladok.successivfordjupning_JSON()),
|
|
2283
|
+
indent=2))
|
|
2284
|
+
print(r"\end{minted}")
|
|
2285
|
+
\end{pycode}
|
|
2286
|
+
|
|
2287
|
+
|
|
2288
|
+
\section{[[undervisningsform_JSON]]}
|
|
2289
|
+
|
|
2290
|
+
<<LadokSession data methods>>=
|
|
2291
|
+
# added by GQMJr
|
|
2292
|
+
def undervisningsform_JSON(self):
|
|
2293
|
+
"""
|
|
2294
|
+
Returns forms of education.
|
|
2295
|
+
"""
|
|
2296
|
+
r = self.session.get(
|
|
2297
|
+
url=self.base_gui_proxy_url +
|
|
2298
|
+
'/kataloginformation/internal/grunddata/undervisningsform',
|
|
2299
|
+
headers=self.headers).json()
|
|
2300
|
+
return r
|
|
2301
|
+
@
|
|
2302
|
+
|
|
2303
|
+
Let's add a test.
|
|
2304
|
+
This should return a dictionary.
|
|
2305
|
+
<<test functions>>=
|
|
2306
|
+
def test_undervisningsform_JSON():
|
|
2307
|
+
r = ladok.undervisningsform_JSON()
|
|
2308
|
+
assert type(r) == dict
|
|
2309
|
+
@
|
|
2310
|
+
|
|
2311
|
+
The output looks like this:
|
|
2312
|
+
\begin{pycode}[apitest]
|
|
2313
|
+
print(r"\begin{minted}{JSON}")
|
|
2314
|
+
print(json.dumps(ladok3.clean_data(ladok.undervisningsform_JSON()), indent=2))
|
|
2315
|
+
print(r"\end{minted}")
|
|
2316
|
+
\end{pycode}
|
|
2317
|
+
|
|
2318
|
+
|
|
2319
|
+
|
|
2320
|
+
\section{[[LokalaPerioder_JSON]]}
|
|
2321
|
+
|
|
2322
|
+
<<LadokSession data methods>>=
|
|
2323
|
+
# added by GQMJr
|
|
2324
|
+
def LokalaPerioder_JSON(self):
|
|
2325
|
+
"""
|
|
2326
|
+
Returns local periods.
|
|
2327
|
+
"""
|
|
2328
|
+
r = self.session.get(
|
|
2329
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/period',
|
|
2330
|
+
headers=self.headers).json()
|
|
2331
|
+
return r
|
|
2332
|
+
@
|
|
2333
|
+
|
|
2334
|
+
Let's add a test.
|
|
2335
|
+
This should return a dictionary.
|
|
2336
|
+
<<test functions>>=
|
|
2337
|
+
def test_LokalaPerioder_JSON():
|
|
2338
|
+
r = ladok.LokalaPerioder_JSON()
|
|
2339
|
+
assert type(r) == dict
|
|
2340
|
+
@
|
|
2341
|
+
|
|
2342
|
+
The output looks like this:
|
|
2343
|
+
\begin{pycode}[apitest]
|
|
2344
|
+
print(r"\begin{minted}{JSON}")
|
|
2345
|
+
print(json.dumps(ladok3.clean_data(ladok.LokalaPerioder_JSON()), indent=2))
|
|
2346
|
+
print(r"\end{minted}")
|
|
2347
|
+
\end{pycode}
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
|
|
2351
|
+
\section{[[nivainomstudieordning_JSON]]}
|
|
2352
|
+
|
|
2353
|
+
<<LadokSession data methods>>=
|
|
2354
|
+
# added by GQMJr
|
|
2355
|
+
def nivainomstudieordning_JSON(self):
|
|
2356
|
+
"""
|
|
2357
|
+
Returns education levels.
|
|
2358
|
+
"""
|
|
2359
|
+
r = self.session.get(
|
|
2360
|
+
url=self.base_gui_proxy_url +
|
|
2361
|
+
'/kataloginformation/internal/grunddata/nivainomstudieordning',
|
|
2362
|
+
headers=self.headers).json()
|
|
2363
|
+
return r
|
|
2364
|
+
@
|
|
2365
|
+
|
|
2366
|
+
Let's add a test.
|
|
2367
|
+
This should return a dictionary.
|
|
2368
|
+
<<test functions>>=
|
|
2369
|
+
def test_nivainomstudieordning_JSON():
|
|
2370
|
+
r = ladok.nivainomstudieordning_JSON()
|
|
2371
|
+
assert type(r) == dict
|
|
2372
|
+
@
|
|
2373
|
+
|
|
2374
|
+
The output looks like this:
|
|
2375
|
+
\begin{pycode}[apitest]
|
|
2376
|
+
print(r"\begin{minted}{JSON}")
|
|
2377
|
+
print(json.dumps(ladok3.clean_data(ladok.nivainomstudieordning_JSON()),
|
|
2378
|
+
indent=2))
|
|
2379
|
+
print(r"\end{minted}")
|
|
2380
|
+
\end{pycode}
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
\section{[[amnesgrupp_JSON]]}
|
|
2385
|
+
|
|
2386
|
+
<<LadokSession data methods>>=
|
|
2387
|
+
# added by GQMJr
|
|
2388
|
+
def amnesgrupp_JSON(self):
|
|
2389
|
+
"""
|
|
2390
|
+
Returns subject area groups.
|
|
2391
|
+
"""
|
|
2392
|
+
r = self.session.get(
|
|
2393
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/amnesgrupp',
|
|
2394
|
+
headers=self.headers).json()
|
|
2395
|
+
return r
|
|
2396
|
+
@
|
|
2397
|
+
|
|
2398
|
+
Let's add a test.
|
|
2399
|
+
This should return a dictionary.
|
|
2400
|
+
<<test functions>>=
|
|
2401
|
+
def test_amnesgrupp_JSON():
|
|
2402
|
+
r = ladok.amnesgrupp_JSON()
|
|
2403
|
+
assert type(r) == dict
|
|
2404
|
+
@
|
|
2405
|
+
|
|
2406
|
+
The output looks like this:
|
|
2407
|
+
\begin{pycode}[apitest]
|
|
2408
|
+
print(r"\begin{minted}{JSON}")
|
|
2409
|
+
print(json.dumps(ladok3.clean_data(ladok.amnesgrupp_JSON()), indent=2))
|
|
2410
|
+
print(r"\end{minted}")
|
|
2411
|
+
\end{pycode}
|
|
2412
|
+
|
|
2413
|
+
|
|
2414
|
+
|
|
2415
|
+
|
|
2416
|
+
\section{[[studietakt_JSON]]}
|
|
2417
|
+
|
|
2418
|
+
<<LadokSession data methods>>=
|
|
2419
|
+
# added by GQMJr
|
|
2420
|
+
def studietakt_JSON(self):
|
|
2421
|
+
"""
|
|
2422
|
+
Returns study paces.
|
|
2423
|
+
"""
|
|
2424
|
+
r = self.session.get(
|
|
2425
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/studietakt',
|
|
2426
|
+
headers=self.headers).json()
|
|
2427
|
+
return r
|
|
2428
|
+
@
|
|
2429
|
+
|
|
2430
|
+
Let's add a test.
|
|
2431
|
+
This should return a dictionary.
|
|
2432
|
+
<<test functions>>=
|
|
2433
|
+
def test_studietakt_JSON():
|
|
2434
|
+
r = ladok.studietakt_JSON()
|
|
2435
|
+
assert type(r) == dict
|
|
2436
|
+
@
|
|
2437
|
+
|
|
2438
|
+
The output looks like this:
|
|
2439
|
+
\begin{pycode}[apitest]
|
|
2440
|
+
print(r"\begin{minted}{JSON}")
|
|
2441
|
+
print(json.dumps(ladok3.clean_data(ladok.studietakt_JSON()), indent=2))
|
|
2442
|
+
print(r"\end{minted}")
|
|
2443
|
+
\end{pycode}
|
|
2444
|
+
|
|
2445
|
+
|
|
2446
|
+
|
|
2447
|
+
|
|
2448
|
+
\section{[[finansieringsform_JSON]]}
|
|
2449
|
+
|
|
2450
|
+
<<LadokSession data methods>>=
|
|
2451
|
+
# added by GQMJr
|
|
2452
|
+
def finansieringsform_JSON(self):
|
|
2453
|
+
"""
|
|
2454
|
+
Returns forms of financing.
|
|
2455
|
+
"""
|
|
2456
|
+
r = self.session.get(
|
|
2457
|
+
url=self.base_gui_proxy_url +
|
|
2458
|
+
'/kataloginformation/internal/grunddata/finansieringsform',
|
|
2459
|
+
headers=self.headers).json()
|
|
2460
|
+
return r
|
|
2461
|
+
@
|
|
2462
|
+
|
|
2463
|
+
Let's add a test.
|
|
2464
|
+
This should return a dictionary.
|
|
2465
|
+
<<test functions>>=
|
|
2466
|
+
def test_finansieringsform_JSON():
|
|
2467
|
+
r = ladok.finansieringsform_JSON()
|
|
2468
|
+
assert type(r) == dict
|
|
2469
|
+
@
|
|
2470
|
+
|
|
2471
|
+
The output looks like this:
|
|
2472
|
+
\begin{pycode}[apitest]
|
|
2473
|
+
print(r"\begin{minted}{JSON}")
|
|
2474
|
+
print(json.dumps(ladok3.clean_data(ladok.finansieringsform_JSON()), indent=2))
|
|
2475
|
+
print(r"\end{minted}")
|
|
2476
|
+
\end{pycode}
|
|
2477
|
+
|
|
2478
|
+
|
|
2479
|
+
\section{[[utbildningsomrade_JSON]]}
|
|
2480
|
+
|
|
2481
|
+
<<LadokSession data methods>>=
|
|
2482
|
+
# added by GQMJr
|
|
2483
|
+
def utbildningsomrade_JSON(self):
|
|
2484
|
+
"""
|
|
2485
|
+
Returns subject areas.
|
|
2486
|
+
"""
|
|
2487
|
+
r = self.session.get(
|
|
2488
|
+
url=self.base_gui_proxy_url +
|
|
2489
|
+
'/kataloginformation/internal/grunddata/utbildningsomrade',
|
|
2490
|
+
headers=self.headers).json()
|
|
2491
|
+
return r
|
|
2492
|
+
@
|
|
2493
|
+
|
|
2494
|
+
Let's add a test.
|
|
2495
|
+
This should return a dictionary.
|
|
2496
|
+
<<test functions>>=
|
|
2497
|
+
def test_utbildningsomrade_JSON():
|
|
2498
|
+
r = ladok.utbildningsomrade_JSON()
|
|
2499
|
+
assert type(r) == dict
|
|
2500
|
+
@
|
|
2501
|
+
|
|
2502
|
+
The output looks like this:
|
|
2503
|
+
\begin{pycode}[apitest]
|
|
2504
|
+
print(r"\begin{minted}{JSON}")
|
|
2505
|
+
print(json.dumps(ladok3.clean_data(ladok.utbildningsomrade_JSON()), indent=2))
|
|
2506
|
+
print(r"\end{minted}")
|
|
2507
|
+
\end{pycode}
|
|
2508
|
+
|
|
2509
|
+
|
|
2510
|
+
\section{[[kravpatidigarestudier_JSON]]}
|
|
2511
|
+
|
|
2512
|
+
<<LadokSession data methods>>=
|
|
2513
|
+
# added by GQMJr
|
|
2514
|
+
def kravpatidigarestudier_JSON(self):
|
|
2515
|
+
"""
|
|
2516
|
+
Returns requirements for earlier studies.
|
|
2517
|
+
"""
|
|
2518
|
+
r = self.session.get(
|
|
2519
|
+
url=self.base_gui_proxy_url +
|
|
2520
|
+
'/kataloginformation/internal/grunddata/kravpatidigarestudier',
|
|
2521
|
+
headers=self.headers).json()
|
|
2522
|
+
return r
|
|
2523
|
+
@
|
|
2524
|
+
|
|
2525
|
+
Let's add a test.
|
|
2526
|
+
This should return a dictionary.
|
|
2527
|
+
<<test functions>>=
|
|
2528
|
+
def test_kravpatidigarestudier_JSON():
|
|
2529
|
+
r = ladok.kravpatidigarestudier_JSON()
|
|
2530
|
+
assert type(r) == dict
|
|
2531
|
+
@
|
|
2532
|
+
|
|
2533
|
+
The output looks like this:
|
|
2534
|
+
\begin{pycode}[apitest]
|
|
2535
|
+
print(r"\begin{minted}{JSON}")
|
|
2536
|
+
print(json.dumps(ladok3.clean_data(ladok.kravpatidigarestudier_JSON()),
|
|
2537
|
+
indent=2))
|
|
2538
|
+
print(r"\end{minted}")
|
|
2539
|
+
\end{pycode}
|
|
2540
|
+
|
|
2541
|
+
|
|
2542
|
+
|
|
2543
|
+
\section{[[studieordning_JSON]]}
|
|
2544
|
+
|
|
2545
|
+
<<LadokSession data methods>>=
|
|
2546
|
+
# added by GQMJr
|
|
2547
|
+
def studieordning_JSON(self):
|
|
2548
|
+
"""
|
|
2549
|
+
Returns study regulations.
|
|
2550
|
+
"""
|
|
2551
|
+
r = self.session.get(
|
|
2552
|
+
url=self.base_gui_proxy_url +
|
|
2553
|
+
'/kataloginformation/internal/grunddata/studieordning',
|
|
2554
|
+
headers=self.headers).json()
|
|
2555
|
+
return r
|
|
2556
|
+
@
|
|
2557
|
+
|
|
2558
|
+
Let's add a test.
|
|
2559
|
+
This should return a dictionary.
|
|
2560
|
+
<<test functions>>=
|
|
2561
|
+
def test_studieordning_JSON():
|
|
2562
|
+
r = ladok.studieordning_JSON()
|
|
2563
|
+
assert type(r) == dict
|
|
2564
|
+
@
|
|
2565
|
+
|
|
2566
|
+
The output looks like this:
|
|
2567
|
+
\begin{pycode}[apitest]
|
|
2568
|
+
print(r"\begin{minted}{JSON}")
|
|
2569
|
+
print(json.dumps(ladok3.clean_data(ladok.studieordning_JSON()), indent=2))
|
|
2570
|
+
print(r"\end{minted}")
|
|
2571
|
+
\end{pycode}
|
|
2572
|
+
|
|
2573
|
+
|
|
2574
|
+
|
|
2575
|
+
\section{[[enhet_JSON]]}
|
|
2576
|
+
|
|
2577
|
+
<<LadokSession data methods>>=
|
|
2578
|
+
# added by GQMJr
|
|
2579
|
+
def enhet_JSON(self):
|
|
2580
|
+
"""
|
|
2581
|
+
Returns credit units.
|
|
2582
|
+
"""
|
|
2583
|
+
r = self.session.get(
|
|
2584
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/grunddata/enhet',
|
|
2585
|
+
headers=self.headers).json()
|
|
2586
|
+
return r
|
|
2587
|
+
@
|
|
2588
|
+
|
|
2589
|
+
Let's add a test.
|
|
2590
|
+
This should return a dictionary.
|
|
2591
|
+
<<test functions>>=
|
|
2592
|
+
def test_enhet_JSON():
|
|
2593
|
+
r = ladok.enhet_JSON()
|
|
2594
|
+
assert type(r) == dict
|
|
2595
|
+
@
|
|
2596
|
+
|
|
2597
|
+
The output looks like this:
|
|
2598
|
+
\begin{pycode}[apitest]
|
|
2599
|
+
print(r"\begin{minted}{JSON}")
|
|
2600
|
+
print(json.dumps(ladok3.clean_data(ladok.enhet_JSON()), indent=2))
|
|
2601
|
+
print(r"\end{minted}")
|
|
2602
|
+
\end{pycode}
|
|
2603
|
+
|
|
2604
|
+
|
|
2605
|
+
\section{[[studielokalisering_JSON]]}
|
|
2606
|
+
|
|
2607
|
+
<<LadokSession data methods>>=
|
|
2608
|
+
# added by GQMJr
|
|
2609
|
+
def studielokalisering_JSON(self):
|
|
2610
|
+
"""
|
|
2611
|
+
Returns study locations.
|
|
2612
|
+
"""
|
|
2613
|
+
r = self.session.get(
|
|
2614
|
+
url=self.base_gui_proxy_url +
|
|
2615
|
+
'/kataloginformation/internal/grunddata/studielokalisering',
|
|
2616
|
+
headers=self.headers).json()
|
|
2617
|
+
return r
|
|
2618
|
+
@
|
|
2619
|
+
|
|
2620
|
+
Let's add a test.
|
|
2621
|
+
This should return a dictionary.
|
|
2622
|
+
<<test functions>>=
|
|
2623
|
+
def test_studielokalisering_JSON():
|
|
2624
|
+
r = ladok.studielokalisering_JSON()
|
|
2625
|
+
assert type(r) == dict
|
|
2626
|
+
@
|
|
2627
|
+
|
|
2628
|
+
The output looks like this:
|
|
2629
|
+
\begin{pycode}[apitest]
|
|
2630
|
+
print(r"\begin{minted}{JSON}")
|
|
2631
|
+
print(json.dumps(ladok3.clean_data(ladok.studielokalisering_JSON()), indent=2))
|
|
2632
|
+
print(r"\end{minted}")
|
|
2633
|
+
\end{pycode}
|
|
2634
|
+
|
|
2635
|
+
|
|
2636
|
+
\section{[[antagningsomgang_JSON]]}
|
|
2637
|
+
|
|
2638
|
+
<<LadokSession data methods>>=
|
|
2639
|
+
# added by GQMJr
|
|
2640
|
+
def antagningsomgang_JSON(self):
|
|
2641
|
+
"""
|
|
2642
|
+
Returns the admission round.
|
|
2643
|
+
"""
|
|
2644
|
+
r = self.session.get(
|
|
2645
|
+
url=self.base_gui_proxy_url +
|
|
2646
|
+
'/kataloginformation/internal/grunddata/antagningsomgang',
|
|
2647
|
+
headers=self.headers).json()
|
|
2648
|
+
return r
|
|
2649
|
+
@
|
|
2650
|
+
|
|
2651
|
+
Let's add a test.
|
|
2652
|
+
This should return a dictionary.
|
|
2653
|
+
<<test functions>>=
|
|
2654
|
+
def test_antagningsomgang_JSON():
|
|
2655
|
+
r = ladok.antagningsomgang_JSON()
|
|
2656
|
+
assert type(r) == dict
|
|
2657
|
+
@
|
|
2658
|
+
|
|
2659
|
+
The output looks like this:
|
|
2660
|
+
\begin{pycode}[apitest]
|
|
2661
|
+
print(r"\begin{minted}{JSON}")
|
|
2662
|
+
print(json.dumps(ladok3.clean_data(ladok.antagningsomgang_JSON()), indent=2))
|
|
2663
|
+
print(r"\end{minted}")
|
|
2664
|
+
\end{pycode}
|
|
2665
|
+
|
|
2666
|
+
|
|
2667
|
+
\section{[[utbildningstyp_JSON]]}
|
|
2668
|
+
|
|
2669
|
+
<<LadokSession data methods>>=
|
|
2670
|
+
# added by GQMJr
|
|
2671
|
+
def utbildningstyp_JSON(self):
|
|
2672
|
+
"""
|
|
2673
|
+
Returns types of education.
|
|
2674
|
+
|
|
2675
|
+
For information about these, see
|
|
2676
|
+
|
|
2677
|
+
https://ladok.se/wp-content/uploads/2018/01/Funktionsbeskrivning_095.pdf
|
|
2678
|
+
"""
|
|
2679
|
+
r = self.session.get(
|
|
2680
|
+
url=self.base_gui_proxy_url +
|
|
2681
|
+
'/kataloginformation/internal/grunddata/utbildningstyp',
|
|
2682
|
+
headers=self.headers).json()
|
|
2683
|
+
return r
|
|
2684
|
+
@
|
|
2685
|
+
|
|
2686
|
+
Let's add a test.
|
|
2687
|
+
This should return a dictionary.
|
|
2688
|
+
<<test functions>>=
|
|
2689
|
+
def test_utbildningstyp_JSON():
|
|
2690
|
+
r = ladok.utbildningstyp_JSON()
|
|
2691
|
+
assert type(r) == dict
|
|
2692
|
+
@
|
|
2693
|
+
|
|
2694
|
+
The output looks like this:
|
|
2695
|
+
\begin{pycode}[apitest]
|
|
2696
|
+
print(r"\begin{minted}{JSON}")
|
|
2697
|
+
print(json.dumps(ladok3.clean_data(ladok.utbildningstyp_JSON()), indent=2))
|
|
2698
|
+
print(r"\end{minted}")
|
|
2699
|
+
\end{pycode}
|
|
2700
|
+
|
|
2701
|
+
|
|
2702
|
+
\section{[[aktivitetstillfallestyp_JSON]]}
|
|
2703
|
+
|
|
2704
|
+
<<LadokSession data methods>>=
|
|
2705
|
+
# added by GQMJr
|
|
2706
|
+
def aktivitetstillfallestyp_JSON(self):
|
|
2707
|
+
"""
|
|
2708
|
+
Returns the activity types.
|
|
2709
|
+
"""
|
|
2710
|
+
r = self.session.get(
|
|
2711
|
+
url=self.base_gui_proxy_url +
|
|
2712
|
+
'/kataloginformation/internal/grunddata/aktivitetstillfallestyp',
|
|
2713
|
+
headers=self.headers).json()
|
|
2714
|
+
return r
|
|
2715
|
+
@
|
|
2716
|
+
|
|
2717
|
+
Let's add a test.
|
|
2718
|
+
This should return a dictionary.
|
|
2719
|
+
<<test functions>>=
|
|
2720
|
+
def test_aktivitetstillfallestyp_JSON():
|
|
2721
|
+
r = ladok.aktivitetstillfallestyp_JSON()
|
|
2722
|
+
assert type(r) == dict
|
|
2723
|
+
@
|
|
2724
|
+
|
|
2725
|
+
The output looks like this:
|
|
2726
|
+
\begin{pycode}[apitest]
|
|
2727
|
+
print(r"\begin{minted}{JSON}")
|
|
2728
|
+
print(json.dumps(ladok3.clean_data(ladok.aktivitetstillfallestyp_JSON()),
|
|
2729
|
+
indent=2))
|
|
2730
|
+
print(r"\end{minted}")
|
|
2731
|
+
\end{pycode}
|
|
2732
|
+
|
|
2733
|
+
|
|
2734
|
+
\section{[[catalog_service_index_JSON]]}
|
|
2735
|
+
|
|
2736
|
+
<<LadokSession data methods>>=
|
|
2737
|
+
# added by GQMJr
|
|
2738
|
+
def catalog_service_index_JSON(self):
|
|
2739
|
+
"""
|
|
2740
|
+
Returns the catalog service index.
|
|
2741
|
+
"""
|
|
2742
|
+
r = self.session.get(
|
|
2743
|
+
url=self.base_gui_proxy_url + '/kataloginformation/internal/service/index',
|
|
2744
|
+
headers=self.headers).json()
|
|
2745
|
+
return r
|
|
2746
|
+
@
|
|
2747
|
+
|
|
2748
|
+
Let's add a test.
|
|
2749
|
+
This should return a dictionary.
|
|
2750
|
+
<<test functions>>=
|
|
2751
|
+
def test_catalog_service_index_JSON():
|
|
2752
|
+
r = ladok.catalog_service_index_JSON()
|
|
2753
|
+
assert type(r) == dict
|
|
2754
|
+
@
|
|
2755
|
+
|
|
2756
|
+
The output looks like this:
|
|
2757
|
+
\begin{pycode}[apitest]
|
|
2758
|
+
print(r"\begin{minted}{JSON}")
|
|
2759
|
+
print(json.dumps(ladok3.clean_data(ladok.catalog_service_index_JSON()),
|
|
2760
|
+
indent=2))
|
|
2761
|
+
print(r"\end{minted}")
|
|
2762
|
+
\end{pycode}
|
|
2763
|
+
|
|
2764
|
+
|
|
2765
|
+
\section{[[omradesbehorighet_JSON]]}
|
|
2766
|
+
|
|
2767
|
+
<<LadokSession data methods>>=
|
|
2768
|
+
# added by GQMJr
|
|
2769
|
+
def omradesbehorighet_JSON(self):
|
|
2770
|
+
"""
|
|
2771
|
+
Returns områdesbehörighet. See
|
|
2772
|
+
|
|
2773
|
+
https://antagning.se/globalassets/omradesbehorigheter-hogskolan.pdf
|
|
2774
|
+
|
|
2775
|
+
for more information.
|
|
2776
|
+
"""
|
|
2777
|
+
r = self.session.get(
|
|
2778
|
+
url=self.base_gui_proxy_url +
|
|
2779
|
+
'/kataloginformation/internal/grunddata/omradesbehorighet',
|
|
2780
|
+
headers=self.headers).json()
|
|
2781
|
+
return r
|
|
2782
|
+
@
|
|
2783
|
+
|
|
2784
|
+
Let's add a test.
|
|
2785
|
+
This should return a dictionary.
|
|
2786
|
+
<<test functions>>=
|
|
2787
|
+
def test_omradesbehorighet_JSON():
|
|
2788
|
+
r = ladok.omradesbehorighet_JSON()
|
|
2789
|
+
assert type(r) == dict
|
|
2790
|
+
@
|
|
2791
|
+
|
|
2792
|
+
The output looks like this:
|
|
2793
|
+
\begin{pycode}[apitest]
|
|
2794
|
+
print(r"\begin{minted}{JSON}")
|
|
2795
|
+
print(json.dumps(ladok3.clean_data(ladok.omradesbehorighet_JSON()), indent=2))
|
|
2796
|
+
print(r"\end{minted}")
|
|
2797
|
+
\end{pycode}
|
|
2798
|
+
|