scratchattach 2.1.15b0__py3-none-any.whl → 3.0.0b0__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.
Files changed (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -273
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -826
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.15b0.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,382 +0,0 @@
1
- """Activity and CloudActivity class"""
2
- from __future__ import annotations
3
-
4
- from bs4 import PageElement
5
-
6
- from . import user, project, studio
7
- from ._base import BaseSiteComponent
8
- from scratchattach.utils import exceptions
9
-
10
-
11
- class Activity(BaseSiteComponent):
12
- """
13
- Represents a Scratch activity (message or other user page activity)
14
- """
15
-
16
- def __repr__(self):
17
- return f"Activity({repr(self.raw)})"
18
-
19
- def __str__(self):
20
- return str(self.raw)
21
-
22
- def __init__(self, **entries):
23
- # Set attributes every Activity object needs to have:
24
- self._session = None
25
- self.raw = None
26
-
27
- # Possible attributes
28
- self.project_id = None
29
- self.gallery_id = None
30
-
31
- self.username = None
32
- self.followed_username = None
33
- self.recipient_username = None
34
-
35
- self.comment_type = None
36
- self.comment_obj_id = None
37
- self.comment_obj_title = None
38
- self.comment_id = None
39
-
40
- self.datetime_created = None
41
- self.time = None
42
- self.type = None
43
-
44
- # Update attributes from entries dict:
45
- self.__dict__.update(entries)
46
-
47
- def update(self):
48
- print("Warning: Activity objects can't be updated")
49
- return False # Objects of this type cannot be updated
50
-
51
- def _update_from_dict(self, data):
52
- self.raw = data
53
- self.__dict__.update(data)
54
- return True
55
-
56
- def _update_from_json(self, data: dict):
57
- """
58
- Update using JSON, used in the classroom API.
59
- """
60
- activity_type = data["type"]
61
-
62
- _time = data["datetime_created"] if "datetime_created" in data else None
63
-
64
- if "actor" in data:
65
- username = data["actor"]["username"]
66
- elif "actor_username" in data:
67
- username = data["actor_username"]
68
- else:
69
- username = None
70
-
71
- if data.get("recipient") is not None:
72
- recipient_username = data["recipient"]["username"]
73
-
74
- elif data.get("recipient_username") is not None:
75
- recipient_username = data["recipient_username"]
76
-
77
- elif data.get("project_creator") is not None:
78
- recipient_username = data["project_creator"]["username"]
79
- else:
80
- recipient_username = None
81
-
82
- default_case = False
83
- # Even if `activity_type` is an invalid value; it will default to 'user performed an action'
84
-
85
- if activity_type == 0:
86
- # follow
87
- followed_username = data["followed_username"]
88
-
89
- self.raw = f"{username} followed user {followed_username}"
90
-
91
- self.datetime_created = _time
92
- self.type = "followuser"
93
- self.username = username
94
- self.followed_username = followed_username
95
-
96
- elif activity_type == 1:
97
- # follow studio
98
- studio_id = data["gallery"]
99
-
100
- raw = f"{username} followed studio https://scratch.mit.edu/studios/{studio_id}"
101
-
102
- self.raw = raw
103
- self.datetime_created = _time
104
- self.type = "followstudio"
105
-
106
- self.username = username
107
- self.gallery_id = studio_id
108
-
109
- elif activity_type == 2:
110
- # love project
111
- project_id = data["project"]
112
-
113
- raw = f"{username} loved project https://scratch.mit.edu/projects/{project_id}"
114
-
115
- self.raw = raw
116
- self.datetime_created = _time,
117
- self.type = "loveproject"
118
-
119
- self.username = username
120
- self.project_id = project_id
121
- self.recipient_username = recipient_username
122
-
123
- elif activity_type == 3:
124
- # Favorite project
125
- project_id = data["project"]
126
-
127
- raw = f"{username} favorited project https://scratch.mit.edu/projects/{project_id}"
128
-
129
- self.raw = raw
130
- self.datetime_created = _time
131
- self.type = "favoriteproject"
132
-
133
- self.username = username
134
- self.project_id = project_id
135
- self.recipient_username = recipient_username
136
-
137
- elif activity_type == 7:
138
- # Add project to studio
139
-
140
- project_id = data["project"]
141
- studio_id = data["gallery"]
142
-
143
- raw = f"{username} added the project https://scratch.mit.edu/projects/{project_id} to studio https://scratch.mit.edu/studios/{studio_id}"
144
-
145
- self.raw = raw
146
- self.datetime_created = _time
147
- self.type = "addprojecttostudio"
148
-
149
- self.username = username
150
- self.project_id = project_id
151
- self.recipient_username = recipient_username
152
-
153
- elif activity_type in (8, 9, 10):
154
- # Share/Reshare project
155
- project_id = data["project"]
156
- is_reshare = data["is_reshare"]
157
-
158
- raw_reshare = "reshared" if is_reshare else "shared"
159
-
160
- raw = f"{username} {raw_reshare} the project https://scratch.mit.edu/projects/{project_id}"
161
-
162
- self.raw = raw
163
- self.datetime_created = _time
164
- self.type = "shareproject"
165
-
166
- self.username = username
167
- self.project_id = project_id
168
- self.recipient_username = recipient_username
169
-
170
- elif activity_type == 11:
171
- # Remix
172
- parent_id = data["parent"]
173
-
174
- raw = f"{username} remixed the project https://scratch.mit.edu/projects/{parent_id}"
175
-
176
- self.raw = raw
177
- self.datetime_created = _time
178
- self.type = "remixproject"
179
-
180
- self.username = username
181
- self.project_id = parent_id
182
- self.recipient_username = recipient_username
183
-
184
- # type 12 does not exist in the HTML. That's why it was removed, not merged with type 13.
185
-
186
- elif activity_type == 13:
187
- # Create ('add') studio
188
- studio_id = data["gallery"]
189
-
190
- raw = f"{username} created the studio https://scratch.mit.edu/studios/{studio_id}"
191
-
192
- self.raw = raw
193
- self.datetime_created = _time
194
- self.type = "createstudio"
195
-
196
- self.username = username
197
- self.gallery_id = studio_id
198
-
199
- elif activity_type == 15:
200
- # Update studio
201
- studio_id = data["gallery"]
202
-
203
- raw = f"{username} updated the studio https://scratch.mit.edu/studios/{studio_id}"
204
-
205
- self.raw = raw
206
- self.datetime_created = _time
207
- self.type = "updatestudio"
208
-
209
- self.username = username
210
- self.gallery_id = studio_id
211
-
212
- elif activity_type in (16, 17, 18, 19):
213
- # Remove project from studio
214
-
215
- project_id = data["project"]
216
- studio_id = data["gallery"]
217
-
218
- raw = f"{username} removed the project https://scratch.mit.edu/projects/{project_id} from studio https://scratch.mit.edu/studios/{studio_id}"
219
-
220
- self.raw = raw
221
- self.datetime_created = _time
222
- self.type = "removeprojectfromstudio"
223
-
224
- self.username = username
225
- self.project_id = project_id
226
-
227
- elif activity_type in (20, 21, 22):
228
- # Was promoted to manager for studio
229
- studio_id = data["gallery"]
230
-
231
- raw = f"{recipient_username} was promoted to manager by {username} for studio https://scratch.mit.edu/studios/{studio_id}"
232
-
233
- self.raw = raw
234
- self.datetime_created = _time
235
- self.type = "promotetomanager"
236
-
237
- self.username = username
238
- self.recipient_username = recipient_username
239
- self.gallery_id = studio_id
240
-
241
- elif activity_type in (23, 24, 25):
242
- # Update profile
243
- raw = f"{username} made a profile update"
244
-
245
- self.raw = raw
246
- self.datetime_created = _time
247
- self.type = "updateprofile"
248
-
249
- self.username = username
250
-
251
- elif activity_type in (26, 27):
252
- # Comment (quite complicated)
253
- comment_type: int = data["comment_type"]
254
- fragment = data["comment_fragment"]
255
- comment_id = data["comment_id"]
256
- comment_obj_id = data["comment_obj_id"]
257
- comment_obj_title = data["comment_obj_title"]
258
-
259
- if comment_type == 0:
260
- # Project comment
261
- raw = f"{username} commented on project https://scratch.mit.edu/projects/{comment_obj_id}/#comments-{comment_id} {fragment!r}"
262
-
263
- elif comment_type == 1:
264
- # Profile comment
265
- raw = f"{username} commented on user https://scratch.mit.edu/users/{comment_obj_title}/#comments-{comment_id} {fragment!r}"
266
-
267
- elif comment_type == 2:
268
- # Studio comment
269
- # Scratch actually provides an incorrect link, but it is fixed here:
270
- raw = f"{username} commented on studio https://scratch.mit.edu/studios/{comment_obj_id}/comments/#comments-{comment_id} {fragment!r}"
271
-
272
- else:
273
- raw = f"{username} commented {fragment!r}" # This should never happen
274
-
275
- self.raw = raw
276
- self.datetime_created = _time
277
- self.type = "addcomment"
278
-
279
- self.username = username
280
-
281
- self.comment_type = comment_type
282
- self.comment_obj_id = comment_obj_id
283
- self.comment_obj_title = comment_obj_title
284
- self.comment_id = comment_id
285
- else:
286
- default_case = True
287
-
288
- if default_case:
289
- # This is coded in the scratch HTML, haven't found an example of it though
290
- raw = f"{username} performed an action."
291
-
292
- self.raw = raw
293
- self.datetime_created = _time
294
- self.type = "performaction"
295
-
296
- self.username = username
297
-
298
- def _update_from_html(self, data: PageElement):
299
-
300
- self.raw = data
301
-
302
- _time = data.find('div').find('span').findNext().findNext().text.strip()
303
-
304
- if '\xa0' in _time:
305
- while '\xa0' in _time:
306
- _time = _time.replace('\xa0', ' ')
307
-
308
- self.datetime_created = _time
309
- self.actor_username = data.find('div').find('span').text
310
-
311
- self.target_name = data.find('div').find('span').findNext().text
312
- self.target_link = data.find('div').find('span').findNext()["href"]
313
- self.target_id = data.find('div').find('span').findNext()["href"].split("/")[-2]
314
-
315
- self.type = data.find('div').find_all('span')[0].next_sibling.strip()
316
- if self.type == "loved":
317
- self.type = "loveproject"
318
-
319
- elif self.type == "favorited":
320
- self.type = "favoriteproject"
321
-
322
- elif "curator" in self.type:
323
- self.type = "becomecurator"
324
-
325
- elif "shared" in self.type:
326
- self.type = "shareproject"
327
-
328
- elif "is now following" in self.type:
329
- if "users" in self.target_link:
330
- self.type = "followuser"
331
- else:
332
- self.type = "followstudio"
333
-
334
- return True
335
-
336
- def actor(self):
337
- """
338
- Returns the user that performed the activity as User object
339
- """
340
- return self._make_linked_object("username", self.actor_username, user.User, exceptions.UserNotFound)
341
-
342
- def target(self):
343
- """
344
- Returns the activity's target (depending on the activity, this is either a User, Project, Studio or Comment object).
345
- May also return None if the activity type is unknown.
346
- """
347
-
348
- if "project" in self.type: # target is a project
349
- if "target_id" in self.__dict__:
350
- return self._make_linked_object("id", self.target_id, project.Project, exceptions.ProjectNotFound)
351
- if "project_id" in self.__dict__:
352
- return self._make_linked_object("id", self.project_id, project.Project, exceptions.ProjectNotFound)
353
-
354
- if self.type == "becomecurator" or self.type == "followstudio": # target is a studio
355
- if "target_id" in self.__dict__:
356
- return self._make_linked_object("id", self.target_id, studio.Studio, exceptions.StudioNotFound)
357
- if "gallery_id" in self.__dict__:
358
- return self._make_linked_object("id", self.gallery_id, studio.Studio, exceptions.StudioNotFound)
359
- # NOTE: the "becomecurator" type is ambigous - if it is inside the studio activity tab, the target is the user who joined
360
- if "username" in self.__dict__:
361
- return self._make_linked_object("username", self.username, user.User, exceptions.UserNotFound)
362
-
363
- if self.type == "followuser" or "curator" in self.type: # target is a user
364
- if "target_name" in self.__dict__:
365
- return self._make_linked_object("username", self.target_name, user.User, exceptions.UserNotFound)
366
- if "followed_username" in self.__dict__:
367
- return self._make_linked_object("username", self.followed_username, user.User, exceptions.UserNotFound)
368
- if "recipient_username" in self.__dict__: # the recipient_username field always indicates the target is a user
369
- return self._make_linked_object("username", self.recipient_username, user.User, exceptions.UserNotFound)
370
-
371
- if self.type == "addcomment": # target is a comment
372
- if self.comment_type == 0:
373
- _c = project.Project(id=self.comment_obj_id, author_name=self._session.username,
374
- _session=self._session).comment_by_id(self.comment_id)
375
- if self.comment_type == 1:
376
- _c = user.User(username=self.comment_obj_title, _session=self._session).comment_by_id(self.comment_id)
377
- if self.comment_type == 2:
378
- _c = user.User(id=self.comment_obj_id, _session=self._session).comment_by_id(self.comment_id)
379
- else:
380
- raise ValueError(f"{self.comment_type} is an invalid comment type")
381
-
382
- return _c
@@ -1,227 +0,0 @@
1
- # classroom alerts (& normal alerts in the future)
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import pprint
7
- import warnings
8
- from dataclasses import dataclass, field, KW_ONLY
9
- from datetime import datetime
10
- from typing import TYPE_CHECKING, Any, Optional, Union
11
- from typing_extensions import Self
12
-
13
- from . import user, project, studio, comment, session
14
- from scratchattach.utils import enums
15
-
16
- if TYPE_CHECKING:
17
- ...
18
-
19
-
20
- # todo: implement regular alerts
21
- # If you implement regular alerts, it may be applicable to make EducatorAlert a subclass.
22
-
23
-
24
- @dataclass
25
- class EducatorAlert:
26
- """
27
- Represents an alert for student activity, viewable at https://scratch.mit.edu/site-api/classrooms/alerts/
28
-
29
- Attributes:
30
- model: The type of alert (presumably); should always equal "educators.educatoralert" in this class
31
- type: An integer that identifies the type of alert, differentiating e.g. against bans or autoban or censored comments etc
32
- raw: The raw JSON data from the API
33
- id: The ID of the alert (internally called 'pk' by scratch, not sure what this is for)
34
- time_read: The time the alert was read
35
- time_created: The time the alert was created
36
- target: The user that the alert is about (the student)
37
- actor: The user that created the alert (the admin)
38
- target_object: The object that the alert is about (e.g. a project, studio, or comment)
39
- notification_type: not sure what this is for, but inferred from the scratch HTML reference
40
- """
41
- _: KW_ONLY
42
- # required attrs
43
- target: user.User
44
- actor: user.User
45
- target_object: Optional[Union[project.Project, studio.Studio, comment.Comment, studio.Studio]]
46
- notification_type: str
47
- _session: Optional[session.Session]
48
-
49
- # defaulted attrs
50
- model: str = "educators.educatoralert"
51
- type: int = -1
52
- raw: dict = field(repr=False, default_factory=dict)
53
- id: int = -1
54
- time_read: datetime = datetime.fromtimestamp(0.0)
55
- time_created: datetime = datetime.fromtimestamp(0.0)
56
-
57
-
58
- @classmethod
59
- def from_json(cls, data: dict[str, Any], _session: Optional[session.Session] = None) -> Self:
60
- """
61
- Load an EducatorAlert from a JSON object.
62
-
63
- Arguments:
64
- data (dict): The JSON object
65
- _session (session.Session): The session object used to load this data, to 'connect' to the alerts rather than just 'get' them
66
-
67
- Returns:
68
- EducatorAlert: The loaded EducatorAlert object
69
- """
70
- model = data.get("model") # With this class, should be equal to educators.educatoralert
71
- assert isinstance(model, str)
72
- alert_id = data.get("pk") # not sure what kind of pk/id this is. Doesn't seem to be a user or class id.
73
- assert isinstance(alert_id, int)
74
-
75
- fields = data.get("fields")
76
- assert isinstance(fields, dict)
77
-
78
- time_read_raw = fields.get("educator_datetime_read")
79
- assert isinstance(time_read_raw, str)
80
- time_read: datetime = datetime.fromisoformat(time_read_raw)
81
-
82
- admin_action = fields.get("admin_action")
83
- assert isinstance(admin_action, dict)
84
-
85
- time_created_raw = admin_action.get("datetime_created")
86
- assert isinstance(time_created_raw, str)
87
- time_created: datetime = datetime.fromisoformat(time_created_raw)
88
-
89
- alert_type = admin_action.get("type")
90
- assert isinstance(alert_type, int)
91
-
92
- target_data = admin_action.get("target_user")
93
- assert isinstance(target_data, dict)
94
- target = user.User(username=target_data.get("username"),
95
- id=target_data.get("pk"),
96
- icon_url=target_data.get("thumbnail_url"),
97
- admin=target_data.get("admin", False),
98
- _session=_session)
99
-
100
- actor_data = admin_action.get("actor")
101
- assert isinstance(actor_data, dict)
102
- actor = user.User(username=actor_data.get("username"),
103
- id=actor_data.get("pk"),
104
- icon_url=actor_data.get("thumbnail_url"),
105
- admin=actor_data.get("admin", False),
106
- _session=_session)
107
-
108
- object_id = admin_action.get("object_id") # this could be a comment id, a project id, etc.
109
- assert isinstance(object_id, int)
110
- target_object: project.Project | studio.Studio | comment.Comment | None = None
111
-
112
- extra_data: dict[str, Any] = json.loads(admin_action.get("extra_data", "{}"))
113
- # todo: if possible, properly implement the incomplete parts of this parser (look for warning.warn())
114
- notification_type: str = ""
115
-
116
- if "project_title" in extra_data:
117
- # project
118
- target_object = project.Project(id=object_id,
119
- title=extra_data["project_title"],
120
- _session=_session)
121
- elif "comment_content" in extra_data:
122
- # comment
123
- comment_data: dict[str, Any] = extra_data["comment_content"]
124
- content: str | None = comment_data.get("content")
125
-
126
- comment_obj_id: int | None = comment_data.get("comment_obj_id")
127
-
128
- comment_type: int | None = comment_data.get("comment_type")
129
-
130
- if comment_type == 0:
131
- # project
132
- comment_source_type = "project"
133
- elif comment_type == 1:
134
- # profile
135
- comment_source_type = "profile"
136
- else:
137
- # probably a studio
138
- comment_source_type = "studio"
139
- warnings.warn(
140
- f"The parser was not able to recognise the \"comment_type\" of {comment_type} in the alert JSON response.\n"
141
- f"Full response: \n{pprint.pformat(data)}.\n\n"
142
- f"Please draft an issue on github: https://github.com/TimMcCool/scratchattach/issues, providing this "
143
- f"whole error message. This will allow us to implement an incomplete part of this parser")
144
-
145
- # the comment_obj's corresponding attribute of comment.Comment is the place() method. As it has no cache, the title data is wasted.
146
- # if the comment_obj is deleted, this is still a valid way of working out the title/username
147
-
148
- target_object = comment.Comment(
149
- id=object_id,
150
- content=content,
151
- source=comment_source_type,
152
- source_id=comment_obj_id,
153
- _session=_session
154
- )
155
-
156
- elif "gallery_title" in extra_data:
157
- # studio
158
- # possible implemented incorrectly
159
- target_object = studio.Studio(
160
- id=object_id,
161
- title=extra_data["gallery_title"],
162
- _session=_session
163
- )
164
- elif "notification_type" in extra_data:
165
- # possible implemented incorrectly
166
- notification_type = extra_data["notification_type"]
167
- else:
168
- warnings.warn(
169
- f"The parser was not able to recognise the \"extra_data\" in the alert JSON response.\n"
170
- f"Full response: \n{pprint.pformat(data)}.\n\n"
171
- f"Please draft an issue on github: https://github.com/TimMcCool/scratchattach/issues, providing this "
172
- f"whole error message. This will allow us to implement an incomplete part of this parser")
173
-
174
- return cls(
175
- id=alert_id,
176
- model=model,
177
- type=alert_type,
178
- raw=data,
179
- time_read=time_read,
180
- time_created=time_created,
181
- target=target,
182
- actor=actor,
183
- target_object=target_object,
184
- notification_type=notification_type,
185
- _session=_session
186
- )
187
-
188
- def __str__(self):
189
- return f"EducatorAlert: {self.message}"
190
-
191
- @property
192
- def alert_type(self) -> enums.AlertType:
193
- """
194
- Get an associated AlertType object for this alert (based on the type index)
195
- """
196
- alert_type = enums.AlertTypes.find(self.type)
197
- if not alert_type:
198
- alert_type = enums.AlertTypes.default.value
199
-
200
- return alert_type
201
-
202
- @property
203
- def message(self):
204
- """
205
- Format the alert message using the alert type's message template, as it would be on the website.
206
- """
207
- raw_message = self.alert_type.message
208
- comment_content = ""
209
- if isinstance(self.target_object, comment.Comment):
210
- comment_content = self.target_object.content
211
-
212
- return raw_message.format(username=self.target.username,
213
- project=self.target_object_title,
214
- studio=self.target_object_title,
215
- notification_type=self.notification_type,
216
- comment=comment_content)
217
-
218
- @property
219
- def target_object_title(self):
220
- """
221
- Get the title of the target object (if applicable)
222
- """
223
- if isinstance(self.target_object, project.Project):
224
- return self.target_object.title
225
- if isinstance(self.target_object, studio.Studio):
226
- return self.target_object.title
227
- return None # explicit