ladok3 3.6__tar.gz → 3.8__tar.gz
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.
Potentially problematic release.
This version of ladok3 might be problematic. Click here for more details.
- {ladok3-3.6 → ladok3-3.8}/PKG-INFO +1 -1
- {ladok3-3.6 → ladok3-3.8}/pyproject.toml +1 -1
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/ladok3.nw +45 -8
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/report.nw +48 -17
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/student.nw +22 -4
- {ladok3-3.6 → ladok3-3.8}/LICENSE +0 -0
- {ladok3-3.6 → ladok3-3.8}/README.md +0 -0
- {ladok3-3.6 → ladok3-3.8}/doc/Makefile +0 -0
- {ladok3-3.6 → ladok3-3.8}/doc/abstract.tex +0 -0
- {ladok3-3.6 → ladok3-3.8}/doc/ladok3.tex +0 -0
- {ladok3-3.6 → ladok3-3.8}/doc/preamble.tex +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/doc.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/exam.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/haskell.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/miun.course.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/miun.depend.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/miun.docs.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/miun.port.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/miun.pub.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/noweb.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/pkg.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/portability.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/pub.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/results.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/subdir.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/tex.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/makefiles/transform.mk +0 -0
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/.gitignore +0 -0
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/Makefile +0 -0
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/api.nw +0 -0
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/cli.nw +0 -0
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/data.nw +0 -0
- {ladok3-3.6 → ladok3-3.8}/src/ladok3/undoc.nw +0 -0
|
@@ -908,6 +908,24 @@ for course in self.ladok.registrations_JSON(self.ladok_id):
|
|
|
908
908
|
@
|
|
909
909
|
|
|
910
910
|
|
|
911
|
+
\section{Is the student suspended?}
|
|
912
|
+
|
|
913
|
+
We also want to know if a student is suspended or not.
|
|
914
|
+
We can get a list of suspensions for a student.
|
|
915
|
+
However, since suspended students are rare, we haven't found a specimen to
|
|
916
|
+
study; so we simply don't know what the list contains.
|
|
917
|
+
But we can return it nonetheless.
|
|
918
|
+
We never cache this result since we always want the most up-to-date version.
|
|
919
|
+
<<student attribute methods>>=
|
|
920
|
+
@property
|
|
921
|
+
def suspensions(self):
|
|
922
|
+
"""
|
|
923
|
+
The list of the students' suspensions.
|
|
924
|
+
"""
|
|
925
|
+
return self.ladok.get_student_suspensions_JSON(self.ladok_id)["Avstangning"]
|
|
926
|
+
@
|
|
927
|
+
|
|
928
|
+
|
|
911
929
|
\chapter{Courses}\label{CourseClasses}
|
|
912
930
|
|
|
913
931
|
We can use the course-related classes as follows.
|
|
@@ -1099,15 +1117,20 @@ print(r"\end{minted}")
|
|
|
1099
1117
|
|
|
1100
1118
|
Now that we have the [[data]] object, we can assign its values to the private
|
|
1101
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.
|
|
1102
1125
|
<<assign CourseInstance data to private attributes>>=
|
|
1103
1126
|
self.__education_id = data.pop("UtbildningUID")
|
|
1104
1127
|
|
|
1105
1128
|
self.__code = data.pop("Utbildningskod")
|
|
1106
1129
|
self.__name = data.pop("Benamning")
|
|
1107
|
-
self.__version = data.pop("Versionsnummer")
|
|
1130
|
+
self.__version = data.pop("Versionsnummer", None)
|
|
1108
1131
|
|
|
1109
|
-
self.__credits = data.pop("Omfattning")
|
|
1110
|
-
self.__unit = data.pop("Enhet")
|
|
1132
|
+
self.__credits = data.pop("Omfattning", None)
|
|
1133
|
+
self.__unit = data.pop("Enhet", None)
|
|
1111
1134
|
|
|
1112
1135
|
self.__grade_scale = self.ladok.get_grade_scales(
|
|
1113
1136
|
id=data.pop("BetygsskalaID"))
|
|
@@ -1126,6 +1149,10 @@ else:
|
|
|
1126
1149
|
The [[CourseComponent]] class will make the attributes available.
|
|
1127
1150
|
We specify the most interesting ones and let the [[LadokData]] constructor turn
|
|
1128
1151
|
the rest into properties as well, so that they are available for the curious.
|
|
1152
|
+
|
|
1153
|
+
Turns out that some course components don't have any credits.
|
|
1154
|
+
This is probably a consequence of being a component in a course with no
|
|
1155
|
+
credits, \eg one of those faux courses to register exchange studies.
|
|
1129
1156
|
<<classes>>=
|
|
1130
1157
|
class CourseComponent(LadokData):
|
|
1131
1158
|
"""Represents a course component of a course registration"""
|
|
@@ -1148,8 +1175,8 @@ class CourseComponent(LadokData):
|
|
|
1148
1175
|
else:
|
|
1149
1176
|
self.__description = description
|
|
1150
1177
|
|
|
1151
|
-
self.__credits = kwargs.pop("Omfattning")
|
|
1152
|
-
self.__unit = kwargs.pop("Enhet")
|
|
1178
|
+
self.__credits = kwargs.pop("Omfattning", None)
|
|
1179
|
+
self.__unit = kwargs.pop("Enhet", None)
|
|
1153
1180
|
|
|
1154
1181
|
ladok = kwargs.pop("ladok")
|
|
1155
1182
|
grade_scale_id = kwargs.pop("BetygsskalaID")
|
|
@@ -1260,6 +1287,12 @@ def __fetch_participants(self):
|
|
|
1260
1287
|
Students register for a course.
|
|
1261
1288
|
The [[CourseRegistration]] class represents this data from LADOK.
|
|
1262
1289
|
This is the object that must be used to get results for a student.
|
|
1290
|
+
This object keeps more info than the course instance.
|
|
1291
|
+
|
|
1292
|
+
We note that some registrations lack the \enquote{Utbildningstillfalleskod}
|
|
1293
|
+
attribute.
|
|
1294
|
+
This happens on \enquote{custom courses} like Erasmus exchanges.
|
|
1295
|
+
If that's the case, we use the value [[None]] in its place.
|
|
1263
1296
|
<<classes>>=
|
|
1264
1297
|
class CourseRegistration(CourseInstance):
|
|
1265
1298
|
"""Represents a student's participation in a course instance"""
|
|
@@ -1268,7 +1301,7 @@ class CourseRegistration(CourseInstance):
|
|
|
1268
1301
|
|
|
1269
1302
|
# ett Ladok-ID för kursomgången
|
|
1270
1303
|
self.__round_id = kwargs.pop("UtbildningstillfalleUID")
|
|
1271
|
-
self.__round_code = kwargs.pop("Utbildningstillfalleskod")
|
|
1304
|
+
self.__round_code = kwargs.pop("Utbildningstillfalleskod", None)
|
|
1272
1305
|
|
|
1273
1306
|
dates = kwargs.pop("Studieperiod")
|
|
1274
1307
|
self.__start = datetime.date.fromisoformat(dates["Startdatum"])
|
|
@@ -1295,10 +1328,10 @@ class CourseRegistration(CourseInstance):
|
|
|
1295
1328
|
return self.__end
|
|
1296
1329
|
|
|
1297
1330
|
def __str__(self):
|
|
1298
|
-
return f"{self.code} {self.round_code} ({self.start}--{self.end})"
|
|
1331
|
+
return f"{self.code} {self.round_code or ''} ({self.start}--{self.end})"
|
|
1299
1332
|
|
|
1300
1333
|
def __repr__(self):
|
|
1301
|
-
return f"{self.code}:{self.round_code}:{self.start}--{self.end}"
|
|
1334
|
+
return f"{self.code}:{self.round_code or ''}:{self.start}--{self.end}"
|
|
1302
1335
|
|
|
1303
1336
|
def results(self, /, **kwargs):
|
|
1304
1337
|
"""Returns the student's results on the course, filtered on keywords"""
|
|
@@ -1444,6 +1477,10 @@ class CourseResult(LadokRemoteData):
|
|
|
1444
1477
|
"""Returns True if the grade has been attested in LADOK"""
|
|
1445
1478
|
return self.__attested
|
|
1446
1479
|
|
|
1480
|
+
def __str__(self):
|
|
1481
|
+
return f"{self.component} {self.grade} " \
|
|
1482
|
+
f"{self.date}{'*' if not self.attested else ''}"
|
|
1483
|
+
|
|
1447
1484
|
def push(self):
|
|
1448
1485
|
if self.__uid:
|
|
1449
1486
|
<<push the existing CourseResult grade data to LADOK>>
|
|
@@ -129,8 +129,8 @@ them.
|
|
|
129
129
|
<<check that we got all positional arguments>>
|
|
130
130
|
try:
|
|
131
131
|
student = ladok.get_student(args.student_id)
|
|
132
|
-
course
|
|
133
|
-
result
|
|
132
|
+
<<look up [[course]] from [[student]]>>
|
|
133
|
+
<<look up [[result]] from [[course]]>>
|
|
134
134
|
result.set_grade(args.grade, args.date)
|
|
135
135
|
if args.finalize:
|
|
136
136
|
result.finalize()
|
|
@@ -139,9 +139,25 @@ except Exception as err:
|
|
|
139
139
|
print(f"{student}: {err}")
|
|
140
140
|
except ValueError as verr:
|
|
141
141
|
print(f"{verr}: {args.student_id}: {err}")
|
|
142
|
-
@
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
@ The option [[args.finalize]] is already set above, so we don't need to add
|
|
143
|
+
that one here.
|
|
144
|
+
|
|
145
|
+
Both looking up the course and the component can yield index errors.
|
|
146
|
+
We'd like to distinguish these.
|
|
147
|
+
We will catch the exception, the reraise the same exception but with a better
|
|
148
|
+
error message.
|
|
149
|
+
<<look up [[course]] from [[student]]>>=
|
|
150
|
+
try:
|
|
151
|
+
course = student.courses(code=args.course_code)[0]
|
|
152
|
+
except IndexError:
|
|
153
|
+
raise Exception("f{args.course_code}: No such course for {student}")
|
|
154
|
+
<<look up [[result]] from [[course]]>>=
|
|
155
|
+
try:
|
|
156
|
+
result = course.results(component=args.component_code)[0]
|
|
157
|
+
except IndexError:
|
|
158
|
+
raise Exception(f"{args.component_code}: "
|
|
159
|
+
f"No such component for {args.course_code}")
|
|
160
|
+
@
|
|
145
161
|
|
|
146
162
|
|
|
147
163
|
\section{Report many results given in standard input}
|
|
@@ -180,23 +196,14 @@ try:
|
|
|
180
196
|
try:
|
|
181
197
|
course = student.courses(code=course_code)[0]
|
|
182
198
|
except IndexError:
|
|
183
|
-
raise Exception(f"{course_code}:
|
|
199
|
+
raise Exception(f"{course_code}: No such course for {student}")
|
|
184
200
|
|
|
185
201
|
try:
|
|
186
202
|
component = course.results(component=component_code)[0]
|
|
187
203
|
except IndexError:
|
|
188
|
-
raise Exception(f"{
|
|
204
|
+
raise Exception(f"{component_code}: no such component for {course_code}")
|
|
189
205
|
|
|
190
|
-
|
|
191
|
-
component.set_grade(grade, date)
|
|
192
|
-
component.finalize()
|
|
193
|
-
if args.verbose:
|
|
194
|
-
print(f"{course_code} {student}: reported "
|
|
195
|
-
f"{component.component} = {component.grade} ({date}).")
|
|
196
|
-
elif component.grade != grade:
|
|
197
|
-
print(f"{course_code} {student}: attested {component.component} "
|
|
198
|
-
f"result {component.grade} ({component.date}) "
|
|
199
|
-
f"is different from {grade} ({date}).")
|
|
206
|
+
<<set [[grade]] to [[component]], output if verbose>>
|
|
200
207
|
except Exception as err:
|
|
201
208
|
try:
|
|
202
209
|
print(f"{course_code} {component_code}={grade} ({date}) {student}: {err}",
|
|
@@ -207,6 +214,30 @@ except Exception as err:
|
|
|
207
214
|
file=sys.stderr)
|
|
208
215
|
@
|
|
209
216
|
|
|
217
|
+
Now, when we set the grade, there are a few cases that should be handled.
|
|
218
|
+
If the grade isn't attested, we try to change it.
|
|
219
|
+
(This might still fail if the grade is finalized but not attested.)
|
|
220
|
+
If we've selected the verbose option, then we print what we have reported.
|
|
221
|
+
|
|
222
|
+
If the grade was attested, then we check if it's different.
|
|
223
|
+
If it's different, we output this.
|
|
224
|
+
If it's the same, we silently ignore it.
|
|
225
|
+
This is best for bulk reporting, because then we can always try to report for
|
|
226
|
+
all students.
|
|
227
|
+
<<set [[grade]] to [[component]], output if verbose>>=
|
|
228
|
+
if not component.attested and component.grade != grade:
|
|
229
|
+
component.set_grade(grade, date)
|
|
230
|
+
if args.finalize:
|
|
231
|
+
component.finalize()
|
|
232
|
+
if args.verbose:
|
|
233
|
+
print(f"{course_code} {student}: reported "
|
|
234
|
+
f"{component.component} = {component.grade} ({date}).")
|
|
235
|
+
elif component.grade != grade:
|
|
236
|
+
print(f"{course_code} {student}: attested {component.component} "
|
|
237
|
+
f"result {component.grade} ({component.date}) "
|
|
238
|
+
f"is different from {grade} ({date}).")
|
|
239
|
+
@
|
|
240
|
+
|
|
210
241
|
|
|
211
242
|
\section{Determine which function to run}
|
|
212
243
|
|
|
@@ -63,6 +63,17 @@ student_parser.add_argument("-c", "--course",
|
|
|
63
63
|
)
|
|
64
64
|
@
|
|
65
65
|
|
|
66
|
+
\subsection{A results flag}
|
|
67
|
+
|
|
68
|
+
We also want a flag for specifying whether or not to include the results of
|
|
69
|
+
each course.
|
|
70
|
+
<<add student command arguments to student parser>>=
|
|
71
|
+
student_parser.add_argument("-r", "--results",
|
|
72
|
+
action="store_true", default=False,
|
|
73
|
+
help="Set to include results for each course listed."
|
|
74
|
+
)
|
|
75
|
+
@
|
|
76
|
+
|
|
66
77
|
\section{Print the student data}
|
|
67
78
|
|
|
68
79
|
Now that we have the student identifier, we can simply use that to fetch the
|
|
@@ -79,7 +90,8 @@ except Exception as err:
|
|
|
79
90
|
print_student_data(student)
|
|
80
91
|
|
|
81
92
|
if args.course:
|
|
82
|
-
|
|
93
|
+
print()
|
|
94
|
+
print_course_data(student, args)
|
|
83
95
|
@
|
|
84
96
|
|
|
85
97
|
\subsection{Printing personal student data}
|
|
@@ -94,16 +106,22 @@ def print_student_data(student):
|
|
|
94
106
|
print(f"Personnummer: {student.personnummer}")
|
|
95
107
|
print(f"LADOK ID: {student.ladok_id}")
|
|
96
108
|
print(f"Alive: {student.alive}")
|
|
109
|
+
print(f"Suspended: {student.suspensions}")
|
|
97
110
|
@
|
|
98
111
|
|
|
99
112
|
\subsection{Printing student's course data}
|
|
100
113
|
|
|
101
114
|
To print the student's course data, we simply filter the courses on the option
|
|
102
115
|
that the user supplies.
|
|
116
|
+
We then print all courses and, if the flag is set, we also print the results
|
|
117
|
+
for each course.
|
|
103
118
|
<<functions>>=
|
|
104
|
-
def print_course_data(student,
|
|
119
|
+
def print_course_data(student, args):
|
|
105
120
|
"""Prints the courses"""
|
|
106
121
|
print("Courses:")
|
|
107
|
-
for course in student.courses(code=course):
|
|
108
|
-
print(f"
|
|
122
|
+
for course in student.courses(code=args.course):
|
|
123
|
+
print(f"{course}")
|
|
124
|
+
if args.results:
|
|
125
|
+
for result in course.results():
|
|
126
|
+
print(f" {result}")
|
|
109
127
|
@
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|