lms.model.scores

  1import typing
  2
  3import edq.util.json
  4import edq.util.time
  5
  6import lms.model.assignments
  7import lms.model.base
  8import lms.model.users
  9
 10class ScoreFragment(edq.util.json.DictConverter):
 11    """ A small subset of information about a score. """
 12
 13    def __init__(self,
 14            score: typing.Union[float, None] = None,
 15            comment: typing.Union[str, None] = None,
 16            **kwargs: typing.Any) -> None:
 17        self.score: typing.Union[float, None] = score
 18        """
 19        The numeric score.
 20        A None value indicates that the score should be cleared.
 21        """
 22
 23        self.comment: typing.Union[str, None] = comment
 24        """ An optional comment for this score. """
 25
 26class AssignmentScore(lms.model.base.BaseType):
 27    """
 28    The score assignment to a student for an assignment (or scorable object).
 29    """
 30
 31    CORE_FIELDS = [
 32        'id', 'user', 'assignment', 'score', 'submission_date', 'graded_date', 'comment',
 33    ]
 34
 35    def __init__(self,
 36            id: typing.Union[str, None] = None,
 37            score: typing.Union[float, None] = None,
 38            submission_date: typing.Union[edq.util.time.Timestamp, None] = None,
 39            graded_date: typing.Union[edq.util.time.Timestamp, None] = None,
 40            comment: typing.Union[str, None] = None,
 41            assignment: typing.Union[lms.model.assignments.AssignmentQuery, None] = None,
 42            user: typing.Union[lms.model.users.UserQuery, None] = None,
 43            **kwargs: typing.Any) -> None:
 44        super().__init__(**kwargs)
 45
 46        self.id: typing.Union[str, None] = id
 47        """ The LMS's identifier for this score. """
 48
 49        self.assignment: typing.Union[lms.model.assignments.AssignmentQuery, None] = assignment
 50        """ The assignment associated with this score. """
 51
 52        self.user: typing.Union[lms.model.users.UserQuery, None] = user
 53        """ The user associated with this score. """
 54
 55        self.score: typing.Union[float, None] = score
 56        """ The assignment score. """
 57
 58        self.submission_date: typing.Union[edq.util.time.Timestamp, None] = submission_date
 59        """ The datetime that the submission that received this score was submitted. """
 60
 61        self.graded_date: typing.Union[edq.util.time.Timestamp, None] = graded_date
 62        """ The datetime that the submission that received this score was graded. """
 63
 64        self.comment: typing.Union[str, None] = comment
 65        """ A comment attached to this score. """
 66
 67    def to_fragment(self) -> ScoreFragment:
 68        """ Get a score fragment from this assignment score. """
 69
 70        return ScoreFragment(score = self.score, comment = self.comment)
 71
 72class Gradebook(lms.model.base.BaseType):
 73    """
 74    A gradebook that contains scores for a set of users and assignments.
 75    """
 76
 77    CORE_FIELDS = [
 78        'assignments', 'users', '_entries',
 79    ]
 80
 81    def __init__(self,
 82            assignments: typing.List[lms.model.assignments.AssignmentQuery],
 83            users: typing.List[lms.model.users.UserQuery],
 84            **kwargs: typing.Any) -> None:
 85        super().__init__(**kwargs)
 86
 87        self.assignments: typing.List[lms.model.assignments.AssignmentQuery] = assignments
 88        """ The assignments represented in this gradebook. """
 89
 90        self.users: typing.List[lms.model.users.UserQuery] = users
 91        """ The users represented in this gradebook. """
 92
 93        self._entries: typing.Dict[str, AssignmentScore] = {}
 94        """ The scores held by this gradebook. """
 95
 96    def _make_key(self, assignment_query: lms.model.assignments.AssignmentQuery, user_query: lms.model.users.UserQuery) -> str:
 97        """ Create a key for this gradebook entry. """
 98
 99        return f"{assignment_query.id}::{user_query.id}"
100
101    def get(self,
102            assignment_query: lms.model.assignments.AssignmentQuery,
103            user_query: lms.model.users.UserQuery,
104            ) -> typing.Union[AssignmentScore, None]:
105        """ Get the target gradebook entry. """
106
107        found_assignment = None
108        for assignment in self.assignments:
109            if (assignment.match(assignment_query)):
110                found_assignment = assignment
111                break
112
113        if (found_assignment is None):
114            return None
115
116        found_user = None
117        for user in self.users:
118            if (user.match(user_query)):
119                found_user = user
120                break
121
122        if (found_user is None):
123            return None
124
125        return self._entries.get(self._make_key(found_assignment, found_user), None)
126
127    def get_scores_by_assignment(self,
128            ) -> typing.Dict[
129                lms.model.assignments.AssignmentQuery,
130                typing.Dict[lms.model.users.UserQuery, AssignmentScore]]:
131        """ Get all entries indexed by assignment. """
132
133        results: typing.Dict[
134                lms.model.assignments.AssignmentQuery,
135                typing.Dict[lms.model.users.UserQuery, AssignmentScore]] = {}
136
137        for assignment in self.assignments:
138            results[assignment] = {}
139
140            for user in self.users:
141                key = self._make_key(assignment, user)
142                if (key not in self._entries):
143                    continue
144
145                results[assignment][user] = self._entries[key]
146
147        return results
148
149    def add(self, score: AssignmentScore) -> None:
150        """
151        Add the score to this gradebook.
152        If the user or assignment is not already in this gradebook, raise an exception.
153
154        The gradebook takes ownership of the score.
155        """
156
157        found_assignment = None
158        for assignment in self.assignments:
159            if (assignment.match(score.assignment)):
160                found_assignment = assignment
161                break
162
163        if (found_assignment is None):
164            raise ValueError(f"Could not match gradebook assignment to score's assignment '{score.assignment}'.")
165
166        found_user = None
167        for user in self.users:
168            if (user.match(score.user)):
169                found_user = user
170                break
171
172        if (found_user is None):
173            raise ValueError(f"Could not match gradebook user to score's user '{score.user}'.")
174
175        # Update the score's queries.
176        score.assignment = found_assignment
177        score.user = found_user
178
179        self._entries[self._make_key(found_assignment, found_user)] = score
180
181    def update_queries(self,
182            assignment_queries: typing.List[lms.model.assignments.ResolvedAssignmentQuery],
183            user_queries: typing.List[lms.model.users.ResolvedUserQuery],
184            ) -> None:
185        """ Update any assignment/user queries with the supplied ones (matching on ID). """
186
187        assignment_map = {query.id: query for query in assignment_queries}
188        user_map = {query.id: query for query in user_queries}
189
190        self.assignments = [assignment_map.get(assignment.id, assignment) for assignment in self.assignments]
191        self.users = [user_map.get(user.id, user) for user in self.users]
192
193        for score in self._entries.values():
194            if (score.assignment is not None):
195                score.assignment = assignment_map.get(score.assignment.id, score.assignment)
196
197            if (score.user is not None):
198                score.user = user_map.get(score.user.id, score.user)
199
200    def as_text_rows(self,
201            skip_headers: bool = False,
202            pretty_headers: bool = False,
203            separator: str = ': ',
204            empty_value: str = '',
205            **kwargs: typing.Any) -> typing.List[str]:
206        rows: typing.List[str] = []
207        for user in sorted(self.users):
208            # Add in a user separator.
209            if (len(rows) > 0):
210                rows.append('')
211
212            rows.append(f"User{separator}{user}")
213
214            for assignment in sorted(self.assignments):
215                score = self.get(assignment, user)
216
217                text = empty_value
218                if (score is not None):
219                    text = str(score.score)
220
221                rows.append(f"{assignment}{separator}{text}")
222
223        return rows
224
225    def get_headers(self,
226            pretty_headers: bool = False,
227            **kwargs: typing.Any) -> typing.List[str]:
228        return ['User'] + [str(query) for query in sorted(self.assignments)]
229
230    def as_table_rows(self,
231            empty_value: str = '',
232            **kwargs: typing.Any) -> typing.List[typing.List[str]]:
233        rows = []
234        for user in sorted(self.users):
235            row = [str(user)]
236            for assignment in sorted(self.assignments):
237                score = self.get(assignment, user)
238
239                text = empty_value
240                if (score is not None):
241                    text = str(score.score)
242
243                row.append(text)
244
245            rows.append(row)
246
247        return rows
248
249    def as_json_dict(self,
250        include_extra_fields: bool = False,
251            **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
252        """
253        Get a dict representation of this object meant for display as JSON.
254        (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.)
255        Calling this method differs from passing this object to json.dumps() (or any sibling),
256        because this method may not include all fields, may flatten or alter fields, and will order fields differently.
257        """
258
259        scores = []
260        for assignment in sorted(self.assignments):
261            row = []
262            for user in sorted(self.users):
263                score: typing.Any = self.get(assignment, user)
264                if (score is not None):
265                    score = score.as_json_dict(include_extra_fields = include_extra_fields, **kwargs)
266
267                row.append(score)
268
269            scores.append(row)
270
271        return {
272            'assignments': self.assignments,
273            'users': self.users,
274            'scores_assignment_user': scores,
275        }
276
277    def __len__(self) -> int:
278        return len(self._entries)
class ScoreFragment(edq.util.json.DictConverter):
11class ScoreFragment(edq.util.json.DictConverter):
12    """ A small subset of information about a score. """
13
14    def __init__(self,
15            score: typing.Union[float, None] = None,
16            comment: typing.Union[str, None] = None,
17            **kwargs: typing.Any) -> None:
18        self.score: typing.Union[float, None] = score
19        """
20        The numeric score.
21        A None value indicates that the score should be cleared.
22        """
23
24        self.comment: typing.Union[str, None] = comment
25        """ An optional comment for this score. """

A small subset of information about a score.

ScoreFragment( score: Optional[float] = None, comment: Optional[str] = None, **kwargs: Any)
14    def __init__(self,
15            score: typing.Union[float, None] = None,
16            comment: typing.Union[str, None] = None,
17            **kwargs: typing.Any) -> None:
18        self.score: typing.Union[float, None] = score
19        """
20        The numeric score.
21        A None value indicates that the score should be cleared.
22        """
23
24        self.comment: typing.Union[str, None] = comment
25        """ An optional comment for this score. """
score: Optional[float]

The numeric score. A None value indicates that the score should be cleared.

comment: Optional[str]

An optional comment for this score.

class AssignmentScore(lms.model.base.BaseType):
27class AssignmentScore(lms.model.base.BaseType):
28    """
29    The score assignment to a student for an assignment (or scorable object).
30    """
31
32    CORE_FIELDS = [
33        'id', 'user', 'assignment', 'score', 'submission_date', 'graded_date', 'comment',
34    ]
35
36    def __init__(self,
37            id: typing.Union[str, None] = None,
38            score: typing.Union[float, None] = None,
39            submission_date: typing.Union[edq.util.time.Timestamp, None] = None,
40            graded_date: typing.Union[edq.util.time.Timestamp, None] = None,
41            comment: typing.Union[str, None] = None,
42            assignment: typing.Union[lms.model.assignments.AssignmentQuery, None] = None,
43            user: typing.Union[lms.model.users.UserQuery, None] = None,
44            **kwargs: typing.Any) -> None:
45        super().__init__(**kwargs)
46
47        self.id: typing.Union[str, None] = id
48        """ The LMS's identifier for this score. """
49
50        self.assignment: typing.Union[lms.model.assignments.AssignmentQuery, None] = assignment
51        """ The assignment associated with this score. """
52
53        self.user: typing.Union[lms.model.users.UserQuery, None] = user
54        """ The user associated with this score. """
55
56        self.score: typing.Union[float, None] = score
57        """ The assignment score. """
58
59        self.submission_date: typing.Union[edq.util.time.Timestamp, None] = submission_date
60        """ The datetime that the submission that received this score was submitted. """
61
62        self.graded_date: typing.Union[edq.util.time.Timestamp, None] = graded_date
63        """ The datetime that the submission that received this score was graded. """
64
65        self.comment: typing.Union[str, None] = comment
66        """ A comment attached to this score. """
67
68    def to_fragment(self) -> ScoreFragment:
69        """ Get a score fragment from this assignment score. """
70
71        return ScoreFragment(score = self.score, comment = self.comment)

The score assignment to a student for an assignment (or scorable object).

AssignmentScore( id: Optional[str] = None, score: Optional[float] = None, submission_date: Optional[edq.util.time.Timestamp] = None, graded_date: Optional[edq.util.time.Timestamp] = None, comment: Optional[str] = None, assignment: Optional[lms.model.assignments.AssignmentQuery] = None, user: Optional[lms.model.users.UserQuery] = None, **kwargs: Any)
36    def __init__(self,
37            id: typing.Union[str, None] = None,
38            score: typing.Union[float, None] = None,
39            submission_date: typing.Union[edq.util.time.Timestamp, None] = None,
40            graded_date: typing.Union[edq.util.time.Timestamp, None] = None,
41            comment: typing.Union[str, None] = None,
42            assignment: typing.Union[lms.model.assignments.AssignmentQuery, None] = None,
43            user: typing.Union[lms.model.users.UserQuery, None] = None,
44            **kwargs: typing.Any) -> None:
45        super().__init__(**kwargs)
46
47        self.id: typing.Union[str, None] = id
48        """ The LMS's identifier for this score. """
49
50        self.assignment: typing.Union[lms.model.assignments.AssignmentQuery, None] = assignment
51        """ The assignment associated with this score. """
52
53        self.user: typing.Union[lms.model.users.UserQuery, None] = user
54        """ The user associated with this score. """
55
56        self.score: typing.Union[float, None] = score
57        """ The assignment score. """
58
59        self.submission_date: typing.Union[edq.util.time.Timestamp, None] = submission_date
60        """ The datetime that the submission that received this score was submitted. """
61
62        self.graded_date: typing.Union[edq.util.time.Timestamp, None] = graded_date
63        """ The datetime that the submission that received this score was graded. """
64
65        self.comment: typing.Union[str, None] = comment
66        """ A comment attached to this score. """
CORE_FIELDS = ['id', 'user', 'assignment', 'score', 'submission_date', 'graded_date', 'comment']

The common fields shared across backends for this type that are used for comparison and other operations. Child classes should set this to define how comparisons are made.

id: Optional[str]

The LMS's identifier for this score.

The assignment associated with this score.

user: Optional[lms.model.users.UserQuery]

The user associated with this score.

score: Optional[float]

The assignment score.

submission_date: Optional[edq.util.time.Timestamp]

The datetime that the submission that received this score was submitted.

graded_date: Optional[edq.util.time.Timestamp]

The datetime that the submission that received this score was graded.

comment: Optional[str]

A comment attached to this score.

def to_fragment(self) -> ScoreFragment:
68    def to_fragment(self) -> ScoreFragment:
69        """ Get a score fragment from this assignment score. """
70
71        return ScoreFragment(score = self.score, comment = self.comment)

Get a score fragment from this assignment score.

class Gradebook(lms.model.base.BaseType):
 73class Gradebook(lms.model.base.BaseType):
 74    """
 75    A gradebook that contains scores for a set of users and assignments.
 76    """
 77
 78    CORE_FIELDS = [
 79        'assignments', 'users', '_entries',
 80    ]
 81
 82    def __init__(self,
 83            assignments: typing.List[lms.model.assignments.AssignmentQuery],
 84            users: typing.List[lms.model.users.UserQuery],
 85            **kwargs: typing.Any) -> None:
 86        super().__init__(**kwargs)
 87
 88        self.assignments: typing.List[lms.model.assignments.AssignmentQuery] = assignments
 89        """ The assignments represented in this gradebook. """
 90
 91        self.users: typing.List[lms.model.users.UserQuery] = users
 92        """ The users represented in this gradebook. """
 93
 94        self._entries: typing.Dict[str, AssignmentScore] = {}
 95        """ The scores held by this gradebook. """
 96
 97    def _make_key(self, assignment_query: lms.model.assignments.AssignmentQuery, user_query: lms.model.users.UserQuery) -> str:
 98        """ Create a key for this gradebook entry. """
 99
100        return f"{assignment_query.id}::{user_query.id}"
101
102    def get(self,
103            assignment_query: lms.model.assignments.AssignmentQuery,
104            user_query: lms.model.users.UserQuery,
105            ) -> typing.Union[AssignmentScore, None]:
106        """ Get the target gradebook entry. """
107
108        found_assignment = None
109        for assignment in self.assignments:
110            if (assignment.match(assignment_query)):
111                found_assignment = assignment
112                break
113
114        if (found_assignment is None):
115            return None
116
117        found_user = None
118        for user in self.users:
119            if (user.match(user_query)):
120                found_user = user
121                break
122
123        if (found_user is None):
124            return None
125
126        return self._entries.get(self._make_key(found_assignment, found_user), None)
127
128    def get_scores_by_assignment(self,
129            ) -> typing.Dict[
130                lms.model.assignments.AssignmentQuery,
131                typing.Dict[lms.model.users.UserQuery, AssignmentScore]]:
132        """ Get all entries indexed by assignment. """
133
134        results: typing.Dict[
135                lms.model.assignments.AssignmentQuery,
136                typing.Dict[lms.model.users.UserQuery, AssignmentScore]] = {}
137
138        for assignment in self.assignments:
139            results[assignment] = {}
140
141            for user in self.users:
142                key = self._make_key(assignment, user)
143                if (key not in self._entries):
144                    continue
145
146                results[assignment][user] = self._entries[key]
147
148        return results
149
150    def add(self, score: AssignmentScore) -> None:
151        """
152        Add the score to this gradebook.
153        If the user or assignment is not already in this gradebook, raise an exception.
154
155        The gradebook takes ownership of the score.
156        """
157
158        found_assignment = None
159        for assignment in self.assignments:
160            if (assignment.match(score.assignment)):
161                found_assignment = assignment
162                break
163
164        if (found_assignment is None):
165            raise ValueError(f"Could not match gradebook assignment to score's assignment '{score.assignment}'.")
166
167        found_user = None
168        for user in self.users:
169            if (user.match(score.user)):
170                found_user = user
171                break
172
173        if (found_user is None):
174            raise ValueError(f"Could not match gradebook user to score's user '{score.user}'.")
175
176        # Update the score's queries.
177        score.assignment = found_assignment
178        score.user = found_user
179
180        self._entries[self._make_key(found_assignment, found_user)] = score
181
182    def update_queries(self,
183            assignment_queries: typing.List[lms.model.assignments.ResolvedAssignmentQuery],
184            user_queries: typing.List[lms.model.users.ResolvedUserQuery],
185            ) -> None:
186        """ Update any assignment/user queries with the supplied ones (matching on ID). """
187
188        assignment_map = {query.id: query for query in assignment_queries}
189        user_map = {query.id: query for query in user_queries}
190
191        self.assignments = [assignment_map.get(assignment.id, assignment) for assignment in self.assignments]
192        self.users = [user_map.get(user.id, user) for user in self.users]
193
194        for score in self._entries.values():
195            if (score.assignment is not None):
196                score.assignment = assignment_map.get(score.assignment.id, score.assignment)
197
198            if (score.user is not None):
199                score.user = user_map.get(score.user.id, score.user)
200
201    def as_text_rows(self,
202            skip_headers: bool = False,
203            pretty_headers: bool = False,
204            separator: str = ': ',
205            empty_value: str = '',
206            **kwargs: typing.Any) -> typing.List[str]:
207        rows: typing.List[str] = []
208        for user in sorted(self.users):
209            # Add in a user separator.
210            if (len(rows) > 0):
211                rows.append('')
212
213            rows.append(f"User{separator}{user}")
214
215            for assignment in sorted(self.assignments):
216                score = self.get(assignment, user)
217
218                text = empty_value
219                if (score is not None):
220                    text = str(score.score)
221
222                rows.append(f"{assignment}{separator}{text}")
223
224        return rows
225
226    def get_headers(self,
227            pretty_headers: bool = False,
228            **kwargs: typing.Any) -> typing.List[str]:
229        return ['User'] + [str(query) for query in sorted(self.assignments)]
230
231    def as_table_rows(self,
232            empty_value: str = '',
233            **kwargs: typing.Any) -> typing.List[typing.List[str]]:
234        rows = []
235        for user in sorted(self.users):
236            row = [str(user)]
237            for assignment in sorted(self.assignments):
238                score = self.get(assignment, user)
239
240                text = empty_value
241                if (score is not None):
242                    text = str(score.score)
243
244                row.append(text)
245
246            rows.append(row)
247
248        return rows
249
250    def as_json_dict(self,
251        include_extra_fields: bool = False,
252            **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
253        """
254        Get a dict representation of this object meant for display as JSON.
255        (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.)
256        Calling this method differs from passing this object to json.dumps() (or any sibling),
257        because this method may not include all fields, may flatten or alter fields, and will order fields differently.
258        """
259
260        scores = []
261        for assignment in sorted(self.assignments):
262            row = []
263            for user in sorted(self.users):
264                score: typing.Any = self.get(assignment, user)
265                if (score is not None):
266                    score = score.as_json_dict(include_extra_fields = include_extra_fields, **kwargs)
267
268                row.append(score)
269
270            scores.append(row)
271
272        return {
273            'assignments': self.assignments,
274            'users': self.users,
275            'scores_assignment_user': scores,
276        }
277
278    def __len__(self) -> int:
279        return len(self._entries)

A gradebook that contains scores for a set of users and assignments.

Gradebook( assignments: List[lms.model.assignments.AssignmentQuery], users: List[lms.model.users.UserQuery], **kwargs: Any)
82    def __init__(self,
83            assignments: typing.List[lms.model.assignments.AssignmentQuery],
84            users: typing.List[lms.model.users.UserQuery],
85            **kwargs: typing.Any) -> None:
86        super().__init__(**kwargs)
87
88        self.assignments: typing.List[lms.model.assignments.AssignmentQuery] = assignments
89        """ The assignments represented in this gradebook. """
90
91        self.users: typing.List[lms.model.users.UserQuery] = users
92        """ The users represented in this gradebook. """
93
94        self._entries: typing.Dict[str, AssignmentScore] = {}
95        """ The scores held by this gradebook. """
CORE_FIELDS = ['assignments', 'users', '_entries']

The common fields shared across backends for this type that are used for comparison and other operations. Child classes should set this to define how comparisons are made.

The assignments represented in this gradebook.

The users represented in this gradebook.

def get( self, assignment_query: lms.model.assignments.AssignmentQuery, user_query: lms.model.users.UserQuery) -> Optional[AssignmentScore]:
102    def get(self,
103            assignment_query: lms.model.assignments.AssignmentQuery,
104            user_query: lms.model.users.UserQuery,
105            ) -> typing.Union[AssignmentScore, None]:
106        """ Get the target gradebook entry. """
107
108        found_assignment = None
109        for assignment in self.assignments:
110            if (assignment.match(assignment_query)):
111                found_assignment = assignment
112                break
113
114        if (found_assignment is None):
115            return None
116
117        found_user = None
118        for user in self.users:
119            if (user.match(user_query)):
120                found_user = user
121                break
122
123        if (found_user is None):
124            return None
125
126        return self._entries.get(self._make_key(found_assignment, found_user), None)

Get the target gradebook entry.

def get_scores_by_assignment( self) -> Dict[lms.model.assignments.AssignmentQuery, Dict[lms.model.users.UserQuery, AssignmentScore]]:
128    def get_scores_by_assignment(self,
129            ) -> typing.Dict[
130                lms.model.assignments.AssignmentQuery,
131                typing.Dict[lms.model.users.UserQuery, AssignmentScore]]:
132        """ Get all entries indexed by assignment. """
133
134        results: typing.Dict[
135                lms.model.assignments.AssignmentQuery,
136                typing.Dict[lms.model.users.UserQuery, AssignmentScore]] = {}
137
138        for assignment in self.assignments:
139            results[assignment] = {}
140
141            for user in self.users:
142                key = self._make_key(assignment, user)
143                if (key not in self._entries):
144                    continue
145
146                results[assignment][user] = self._entries[key]
147
148        return results

Get all entries indexed by assignment.

def add(self, score: AssignmentScore) -> None:
150    def add(self, score: AssignmentScore) -> None:
151        """
152        Add the score to this gradebook.
153        If the user or assignment is not already in this gradebook, raise an exception.
154
155        The gradebook takes ownership of the score.
156        """
157
158        found_assignment = None
159        for assignment in self.assignments:
160            if (assignment.match(score.assignment)):
161                found_assignment = assignment
162                break
163
164        if (found_assignment is None):
165            raise ValueError(f"Could not match gradebook assignment to score's assignment '{score.assignment}'.")
166
167        found_user = None
168        for user in self.users:
169            if (user.match(score.user)):
170                found_user = user
171                break
172
173        if (found_user is None):
174            raise ValueError(f"Could not match gradebook user to score's user '{score.user}'.")
175
176        # Update the score's queries.
177        score.assignment = found_assignment
178        score.user = found_user
179
180        self._entries[self._make_key(found_assignment, found_user)] = score

Add the score to this gradebook. If the user or assignment is not already in this gradebook, raise an exception.

The gradebook takes ownership of the score.

def update_queries( self, assignment_queries: List[lms.model.assignments.ResolvedAssignmentQuery], user_queries: List[lms.model.users.ResolvedUserQuery]) -> None:
182    def update_queries(self,
183            assignment_queries: typing.List[lms.model.assignments.ResolvedAssignmentQuery],
184            user_queries: typing.List[lms.model.users.ResolvedUserQuery],
185            ) -> None:
186        """ Update any assignment/user queries with the supplied ones (matching on ID). """
187
188        assignment_map = {query.id: query for query in assignment_queries}
189        user_map = {query.id: query for query in user_queries}
190
191        self.assignments = [assignment_map.get(assignment.id, assignment) for assignment in self.assignments]
192        self.users = [user_map.get(user.id, user) for user in self.users]
193
194        for score in self._entries.values():
195            if (score.assignment is not None):
196                score.assignment = assignment_map.get(score.assignment.id, score.assignment)
197
198            if (score.user is not None):
199                score.user = user_map.get(score.user.id, score.user)

Update any assignment/user queries with the supplied ones (matching on ID).

def as_text_rows( self, skip_headers: bool = False, pretty_headers: bool = False, separator: str = ': ', empty_value: str = '', **kwargs: Any) -> List[str]:
201    def as_text_rows(self,
202            skip_headers: bool = False,
203            pretty_headers: bool = False,
204            separator: str = ': ',
205            empty_value: str = '',
206            **kwargs: typing.Any) -> typing.List[str]:
207        rows: typing.List[str] = []
208        for user in sorted(self.users):
209            # Add in a user separator.
210            if (len(rows) > 0):
211                rows.append('')
212
213            rows.append(f"User{separator}{user}")
214
215            for assignment in sorted(self.assignments):
216                score = self.get(assignment, user)
217
218                text = empty_value
219                if (score is not None):
220                    text = str(score.score)
221
222                rows.append(f"{assignment}{separator}{text}")
223
224        return rows

Create a representation of this object in the "text" style of this project meant for display. A list of rows will be returned.

def get_headers(self, pretty_headers: bool = False, **kwargs: Any) -> List[str]:
226    def get_headers(self,
227            pretty_headers: bool = False,
228            **kwargs: typing.Any) -> typing.List[str]:
229        return ['User'] + [str(query) for query in sorted(self.assignments)]

Get a list of headers to label the values represented by this object meant for display. This method is a companion to as_table_rows(), given the same options these two methods will produce rows with the same length and ordering.

def as_table_rows(self, empty_value: str = '', **kwargs: Any) -> List[List[str]]:
231    def as_table_rows(self,
232            empty_value: str = '',
233            **kwargs: typing.Any) -> typing.List[typing.List[str]]:
234        rows = []
235        for user in sorted(self.users):
236            row = [str(user)]
237            for assignment in sorted(self.assignments):
238                score = self.get(assignment, user)
239
240                text = empty_value
241                if (score is not None):
242                    text = str(score.score)
243
244                row.append(text)
245
246            rows.append(row)
247
248        return rows

Get a list of the values by this object meant for display. This method is a companion to get_headers(), given the same options these two methods will produce rows with the same length and ordering.

Note that the default implementation for this method always return a single row, but children may override and return multiple rows per object.

def as_json_dict( self, include_extra_fields: bool = False, **kwargs: Any) -> Dict[str, Any]:
250    def as_json_dict(self,
251        include_extra_fields: bool = False,
252            **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
253        """
254        Get a dict representation of this object meant for display as JSON.
255        (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.)
256        Calling this method differs from passing this object to json.dumps() (or any sibling),
257        because this method may not include all fields, may flatten or alter fields, and will order fields differently.
258        """
259
260        scores = []
261        for assignment in sorted(self.assignments):
262            row = []
263            for user in sorted(self.users):
264                score: typing.Any = self.get(assignment, user)
265                if (score is not None):
266                    score = score.as_json_dict(include_extra_fields = include_extra_fields, **kwargs)
267
268                row.append(score)
269
270            scores.append(row)
271
272        return {
273            'assignments': self.assignments,
274            'users': self.users,
275            'scores_assignment_user': scores,
276        }

Get a dict representation of this object meant for display as JSON. (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.) Calling this method differs from passing this object to json.dumps() (or any sibling), because this method may not include all fields, may flatten or alter fields, and will order fields differently.