qcanvas 0.0.5a0__py3-none-any.whl → 0.0.5.2a0__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.

Potentially problematic release.


This version of qcanvas might be problematic. Click here for more details.

@@ -132,6 +132,27 @@ class CanvasClient(SelfAuthenticating):
132
132
 
133
133
  return LegacyFile.from_dict(json.loads(response.text))
134
134
 
135
+ @retry(
136
+ wait=wait_exponential(exp_base=1.2, max=10) + wait_random(0, 1),
137
+ retry=retry_if_exception_type(RatelimitedException),
138
+ stop=stop_after_attempt(8)
139
+ )
140
+ async def get_temp_session_link(self) -> str | None:
141
+ """
142
+ Gets the link which will authenticate a browser/open canvas using a 'legacy session token'
143
+ Returns
144
+ -------
145
+ str | None
146
+ The url as a string if the request succeeded, None if it didn't
147
+ """
148
+
149
+ token_response = await self.client.get(self.canvas_url.join("login/session_token"), **self.get_headers())
150
+
151
+ if token_response.is_success:
152
+ return json.loads(token_response.text)["session_url"]
153
+ else:
154
+ return None
155
+
135
156
  @retry(
136
157
  stop=stop_after_attempt(3),
137
158
  wait=wait_fixed(5) + wait_random(0, 1),
@@ -1,4 +1,3 @@
1
- from PySide6.QtCore import Qt
2
1
  from PySide6.QtGui import QStandardItem
3
2
 
4
3
  from qcanvas.util import tree_util as tree
@@ -9,7 +8,4 @@ class ContainerItem(QStandardItem):
9
8
  super().__init__()
10
9
  self.content = data
11
10
  self.setEditable(False)
12
-
13
- def data(self, role=257):
14
- if role == Qt.ItemDataRole.DisplayRole:
15
- return self.content.text
11
+ self.setText(data.text)
qcanvas/ui/main_ui.py CHANGED
@@ -3,8 +3,8 @@ import sys
3
3
  import traceback
4
4
  from typing import Sequence, Optional
5
5
 
6
- from PySide6.QtCore import Slot, Signal, Qt, QUrl
7
- from PySide6.QtGui import QDesktopServices
6
+ from PySide6.QtCore import Slot, Signal, Qt, QUrl, QObject
7
+ from PySide6.QtGui import QDesktopServices, QKeySequence
8
8
  from PySide6.QtWidgets import *
9
9
  from qasync import asyncSlot
10
10
 
@@ -20,9 +20,10 @@ from qcanvas.util import self_updater
20
20
  from qcanvas.util.app_settings import settings
21
21
  from qcanvas.util.constants import app_name
22
22
  from qcanvas.util.course_indexer import DataManager
23
+ from qcanvas.util.helpers.qaction_helper import create_qaction
23
24
 
24
25
  _aux_settings = settings.auxiliary
25
-
26
+ _no_course_selected_text = "No course selected"
26
27
 
27
28
  class AppMainWindow(QMainWindow):
28
29
  logger = logging.getLogger()
@@ -62,17 +63,15 @@ class AppMainWindow(QMainWindow):
62
63
  self.tab_widget.insertTab(1, self.assignment_viewer, "Assignments")
63
64
  self.tab_widget.insertTab(2, self.pages_viewer, "Pages")
64
65
 
65
- h_layout = QHBoxLayout()
66
- h_layout.addWidget(self.course_list)
67
- h_layout.addWidget(self.tab_widget)
68
- h_layout.setStretch(1, 1)
66
+ self.course_name_label = QLabel(_no_course_selected_text)
67
+ self.course_name_label.setStyleSheet("font-weight: bold;")
68
+ course_stack_layout = self.create_layout_and_add_widgets(QVBoxLayout, self.course_name_label, self.tab_widget)
69
69
 
70
- v_layout = QVBoxLayout()
71
- v_layout.addLayout(h_layout)
72
- v_layout.addWidget(self.sync_button)
70
+ h_layout = self.create_layout_and_add_widgets(QHBoxLayout, self.course_list, course_stack_layout)
71
+ h_layout.setStretch(1, 1)
73
72
 
74
73
  widget = QWidget()
75
- widget.setLayout(v_layout)
74
+ widget.setLayout(self.create_layout_and_add_widgets(QVBoxLayout, h_layout, self.sync_button))
76
75
  self.setCentralWidget(widget)
77
76
 
78
77
  self.setup_menu_bar()
@@ -90,12 +89,26 @@ class AppMainWindow(QMainWindow):
90
89
  # Set its height so it doesn't get bigger when there's a progress bar in it
91
90
  bar.setFixedHeight(bar.height())
92
91
 
92
+ @staticmethod
93
+ def create_layout_and_add_widgets(layout_type: type, *widgets) -> QLayout:
94
+ layout = layout_type()
95
+
96
+ for widget in widgets:
97
+ if isinstance(widget, QLayout):
98
+ layout.addLayout(widget)
99
+ else:
100
+ layout.addWidget(widget)
101
+
102
+ return layout
103
+
93
104
  def setup_menu_bar(self):
94
105
  menu_bar = self.menuBar()
95
106
 
96
- menu_bar.addMenu(ThemeSelectionMenu())
97
- view_menu = menu_bar.addMenu("View")
107
+ app_menu: QMenu = menu_bar.addMenu("App")
108
+ view_menu: QMenu = menu_bar.addMenu("View")
98
109
 
110
+ app_menu.addAction(self.setup_quick_authentication_action(app_menu))
111
+ app_menu.addMenu(ThemeSelectionMenu())
99
112
  view_menu.addMenu(self.setup_group_by_menu())
100
113
 
101
114
  def setup_group_by_menu(self) -> QMenu:
@@ -105,6 +118,18 @@ class AppMainWindow(QMainWindow):
105
118
 
106
119
  return file_grouping_menu
107
120
 
121
+ def setup_quick_authentication_action(self, parent: QObject):
122
+ return create_qaction(
123
+ name="Quick canvas login",
124
+ shortcut=QKeySequence("Ctrl+O"),
125
+ triggered=self.open_quick_auth_in_browser,
126
+ parent=parent
127
+ )
128
+
129
+ @asyncSlot()
130
+ async def open_quick_auth_in_browser(self):
131
+ QDesktopServices.openUrl(await self.data_manager.client.get_temp_session_link())
132
+
108
133
  def closeEvent(self, event):
109
134
  settings.geometry = self.saveGeometry()
110
135
  settings.window_state = self.saveState()
@@ -205,9 +230,11 @@ class AppMainWindow(QMainWindow):
205
230
  self.pages_viewer.fill_tree(course)
206
231
  self.assignment_viewer.fill_tree(course)
207
232
  self.file_viewer.load_course_files(course)
233
+ self.course_name_label.setText(course.name)
208
234
  else:
209
235
  self.selected_course = None
210
236
  self.file_viewer.clear()
237
+ self.course_name_label.setText(_no_course_selected_text)
211
238
 
212
239
  @asyncSlot(db.CoursePreferences)
213
240
  async def on_grouping_preference_changed(self):
@@ -56,6 +56,7 @@ class CourseList(QTreeView):
56
56
  for term, courses in self.group_courses_by_term(courses):
57
57
  term_node = QStandardItem(term.name)
58
58
  term_node.setEditable(False)
59
+ term_node.setSelectable(False)
59
60
 
60
61
  for course in courses:
61
62
  course_node = CourseNode(course)
@@ -10,6 +10,7 @@ import qcanvas.db as db
10
10
  from qcanvas.ui.container_item import ContainerItem
11
11
  from qcanvas.util.constants import default_assignments_module_names
12
12
  from qcanvas.util.course_indexer import resource_helpers
13
+ from qcanvas.util.helpers import canvas_sanitiser
13
14
  from qcanvas.util.linkscanner import ResourceScanner
14
15
 
15
16
 
@@ -97,7 +98,7 @@ class PageLikeViewer(QWidget):
97
98
  return
98
99
 
99
100
  # todo when a file is finished downloading it would be nice if the page was refreshed to show the state properly
100
- html = canvas_garbage_remover.remove_stylesheets_from_html(item.content)
101
+ html = canvas_sanitiser.remove_stylesheets_from_html(item.content)
101
102
  self.viewer.setHtml(self.link_transformer.transform_links(html))
102
103
 
103
104
 
@@ -113,6 +114,7 @@ class PagesViewer(PageLikeViewer):
113
114
  continue
114
115
 
115
116
  module_node = ContainerItem(module)
117
+ module_node.setSelectable(False)
116
118
 
117
119
  for module_item in list[db.ModuleItem](module.items):
118
120
  module_node.appendRow(ContainerItem(module_item))
@@ -83,7 +83,7 @@ class DataManager:
83
83
  sessionmaker: AsyncSessionMaker,
84
84
  link_scanners: Sequence[ResourceScanner]):
85
85
 
86
- self._client = client
86
+ self.client = client
87
87
  self._link_scanners = link_scanners
88
88
  self._session_maker = sessionmaker
89
89
 
@@ -188,7 +188,7 @@ class DataManager:
188
188
 
189
189
  async def synchronize_with_canvas(self, progress_reporter: ProgressReporter = noop_reporter):
190
190
  section = progress_reporter.section("Loading index", 0)
191
- raw_query = (await self._client.do_graphql_query(gql(queries.all_courses.DEFINITION), detailed=True))
191
+ raw_query = (await self.client.do_graphql_query(gql(queries.all_courses.DEFINITION), detailed=True))
192
192
  section.increment_progress()
193
193
 
194
194
  await self.load_courses_data(queries.AllCoursesQueryData(**raw_query).all_courses, progress_reporter)
@@ -343,7 +343,7 @@ class DataManager:
343
343
  Fetches information about the specified file from canvas
344
344
  """
345
345
  _logger.debug(f"Fetching file (for module file) %s %s", file.m_id, file.display_name)
346
- result = await self._client.get_file(file.m_id, course_id)
346
+ result = await self.client.get_file(file.m_id, course_id)
347
347
  resource = db.convert_file(file, result.size)
348
348
  resource.id = f"{canvas_resource_id_prefix}:{resource.id}"
349
349
  resource.course_id = course_id
@@ -369,7 +369,7 @@ class DataManager:
369
369
 
370
370
  try:
371
371
  # Get the page
372
- result = await self._client.get_page(page.m_id, course_id)
372
+ result = await self.client.get_page(page.m_id, course_id)
373
373
  except BaseException as e:
374
374
  # Handle any errors
375
375
  _logger.error(e)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qcanvas
3
- Version: 0.0.5a0
3
+ Version: 0.0.5.2a0
4
4
  Summary: A canvas client
5
5
  Author: QCanvas
6
6
  Classifier: Operating System :: OS Independent
@@ -11,7 +11,7 @@ qcanvas/net/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  qcanvas/net/custom_httpx_async_transport.py,sha256=1OyszJO8TrwDrq-no-angb-AIjgq_GiV67tQHuPxmm0,1227
12
12
  qcanvas/net/self_authenticating.py,sha256=bF0lq9BgDQg5We4l0Z_JpJhI8ZGC7TTgggMWHUOYw3U,2515
13
13
  qcanvas/net/canvas/__init__.py,sha256=EPLFN05Vx9yNHDLK-eUp97C6ymtMTnscwdn28OBlNJw,96
14
- qcanvas/net/canvas/canvas_client.py,sha256=BI5JBf47r3RlCf3HGTzcz2zSBPBT2k32fbxllbMOJF8,8921
14
+ qcanvas/net/canvas/canvas_client.py,sha256=eENktdmVnyd6eIh6I4dH-5mU09pfJLo-v1xQ8Oma26k,9674
15
15
  qcanvas/net/canvas/legacy_canvas_types.py,sha256=pKuTBh1m5oSq9e7jmbHzYfKeoPSw0uHAqLwddKZpsr4,3942
16
16
  qcanvas/queries/__init__.py,sha256=tRUwMS1OvYIWRd4WpmAHrDrcelj_dLEaesxh9NngC6s,227
17
17
  qcanvas/queries/all_courses.gql,sha256=8U0_3VOcD66SRCfvhZ9IOTmxVgDPJdMD_LuGPbM9vVU,124
@@ -19,18 +19,18 @@ qcanvas/queries/all_courses.py,sha256=-DpMy7eDFUeYp3ffZrwXp15qo_t6Lfx8K-rFi3OK1D
19
19
  qcanvas/queries/canvas_course_data.gql,sha256=KJHjdu5sMKld4L6mPGfUrhqRLx_wdgGH8fwTnmRFGrI,1115
20
20
  qcanvas/queries/canvas_course_data.py,sha256=_ZNv9stXAyap2eBbCqmajXVpWr0920ZUcCS6Gtyyo0g,4314
21
21
  qcanvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- qcanvas/ui/container_item.py,sha256=fArazXUnUaO4r3Sk_XW_cpbyX7qDfntp74sFGsVDMEw,399
23
- qcanvas/ui/main_ui.py,sha256=5qSlaerXsgjhWiyt9wQ6fH5YWYAx9oCBZnpm3Qodo7g,8488
22
+ qcanvas/ui/container_item.py,sha256=MNGL3Z6dNlM59XXWqmXB1Ql3FTOEdA6TY2K9Do1LmTE,285
23
+ qcanvas/ui/main_ui.py,sha256=2vpBJ_5DZit8ZYo-fIWk7x9zRarVwO-zu1cnXm5QMMc,9824
24
24
  qcanvas/ui/setup_dialog.py,sha256=BXDNDd5IjA0l-9teUYsOCYMRXxUl4Ryxx64GsxJX5xU,7261
25
25
  qcanvas/ui/status_bar_reporter.py,sha256=6Qp0i-vUq9Yxowx0BSB4ieR8BO37iKjRNvPQ1AHUXtI,1318
26
26
  qcanvas/ui/menu_bar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  qcanvas/ui/menu_bar/grouping_preferences_menu.py,sha256=nSe9qt3pI9WAiKVTOEtHN9JPW3gaDxpU8YiSGGRjR5M,2407
28
28
  qcanvas/ui/menu_bar/theme_selection_menu.py,sha256=WdcFMEO-FRYJm3qKjYbUpRhJPVe0-RhwrVxcyckSr0Q,1248
29
29
  qcanvas/ui/viewer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- qcanvas/ui/viewer/course_list.py,sha256=uEK4De9Zu7n-8oUIaHXPcBh7-Ul_tXAiKfyqCJOHeeY,3370
30
+ qcanvas/ui/viewer/course_list.py,sha256=jg_t66uyZpWyBITRWBFHaYgYMvWJ5useMljWv4tfOx4,3413
31
31
  qcanvas/ui/viewer/file_list.py,sha256=hqKXrLs4bLHA9himFEuE8d8Pq2TJnvuUGGJgZftiyrA,7869
32
32
  qcanvas/ui/viewer/file_view_tab.py,sha256=DrXwHwIPBoHVjsKU3zUbOvA4tTarp29d4cMYvZnE-O8,2205
33
- qcanvas/ui/viewer/page_list_viewer.py,sha256=vcVRF8sqVbVJ-mXvbZBzNbAv9C3-eAInvU-Xl44g48s,5446
33
+ qcanvas/ui/viewer/page_list_viewer.py,sha256=-5wtDEX6TMSMNYyCxWAyf2nIBuIEPSgcmSaiDNcWQrQ,5535
34
34
  qcanvas/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  qcanvas/util/app_settings.py,sha256=B9hIfl6wQZkBflM1_UwpCEd935k7sckegXXwKMbSnYo,2940
36
36
  qcanvas/util/constants.py,sha256=GNRlqG9rnWlW5ijKoJEsyuOWTdrXNSiWL_a5XeEZzI0,259
@@ -40,7 +40,7 @@ qcanvas/util/self_updater.py,sha256=oItk-J-CSN74RGu4P_xtJ4S8vGnaAZL23Bm4jpLbLzg,
40
40
  qcanvas/util/task_pool.py,sha256=kizChooliF7O9w5LtgBd87LHT_M1aGXiPoJ-M8Lg2RY,8159
41
41
  qcanvas/util/course_indexer/__init__.py,sha256=IqwTfB8KVk7Go_yuBL8NOANgFiBCzjt0z38D4CEgq7w,38
42
42
  qcanvas/util/course_indexer/conversion_helpers.py,sha256=DZwx3Ct-MYzxQwuvqem5c2cARSkr_2SJvz0gaZDJq6c,2644
43
- qcanvas/util/course_indexer/data_manager.py,sha256=JcEkmaQDZlvb7vcfBBTi8KAbgyzu5OVxsm0gWyyTpHA,17049
43
+ qcanvas/util/course_indexer/data_manager.py,sha256=AsDP-PLzFquL-d0l9uFyAihcfLbRGuCYoQV4uySk_uE,17045
44
44
  qcanvas/util/course_indexer/resource_helpers.py,sha256=54XnTbcq8RG1zSDifycSYdpQ007_aQrFFXkibMO7l8k,6794
45
45
  qcanvas/util/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  qcanvas/util/helpers/canvas_sanitiser.py,sha256=c6r6cyJomQ1zn5wFK-LX-ZsZXQZ-A2awBFtCB_JqUtE,1184
@@ -56,6 +56,6 @@ qcanvas/util/tree_util/__init__.py,sha256=yqjNHrD8SiyW_fGPfw7xdS-Rctjdu-RMWSkUxc
56
56
  qcanvas/util/tree_util/expanding_tree.py,sha256=5UM9kfcHtVavzAc9nwd7lucVnjI_dkVH8qC3GvmBvtc,6622
57
57
  qcanvas/util/tree_util/model_helpers.py,sha256=io1bHOMNgVqkPvGH2Iohug1-wx2MwC_-qlWlpVA0fFE,743
58
58
  qcanvas/util/tree_util/tree_model.py,sha256=JhlYmGqxlul8jUUQ-KtPXIehzgG-UtszHve7Wh-XbtE,2981
59
- qcanvas-0.0.5a0.dist-info/METADATA,sha256=0BtGMzx6QAWQ9BRnWZiGu3gmeMc1XlIcg-MhXyZOYzY,674
60
- qcanvas-0.0.5a0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
61
- qcanvas-0.0.5a0.dist-info/RECORD,,
59
+ qcanvas-0.0.5.2a0.dist-info/METADATA,sha256=sYVc14zPHkIRzrCAslhOP8ABZspmiU-ciMj4sAskgQ4,676
60
+ qcanvas-0.0.5.2a0.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
61
+ qcanvas-0.0.5.2a0.dist-info/RECORD,,