qcanvas 0.0.5.7a0__py3-none-any.whl → 1.0.3.post1__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.

Files changed (114) hide show
  1. qcanvas/app_start/__init__.py +47 -0
  2. qcanvas/backend_connectors/__init__.py +2 -0
  3. qcanvas/backend_connectors/frontend_resource_manager.py +63 -0
  4. qcanvas/backend_connectors/qcanvas_task_master.py +28 -0
  5. qcanvas/icons/__init__.py +6 -0
  6. qcanvas/icons/file-download-failed.svg +6 -0
  7. qcanvas/icons/file-downloaded.svg +6 -0
  8. qcanvas/icons/file-not-downloaded.svg +6 -0
  9. qcanvas/icons/file-unknown.svg +6 -0
  10. qcanvas/icons/icons.qrc +4 -0
  11. qcanvas/icons/main_icon.svg +7 -7
  12. qcanvas/icons/rc_icons.py +580 -214
  13. qcanvas/icons/sync.svg +6 -6
  14. qcanvas/run.py +29 -0
  15. qcanvas/ui/course_viewer/__init__.py +2 -0
  16. qcanvas/ui/course_viewer/content_tree.py +123 -0
  17. qcanvas/ui/course_viewer/course_tree.py +93 -0
  18. qcanvas/ui/course_viewer/course_viewer.py +62 -0
  19. qcanvas/ui/course_viewer/tabs/__init__.py +3 -0
  20. qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py +1 -0
  21. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +168 -0
  22. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +104 -0
  23. qcanvas/ui/course_viewer/tabs/content_tab.py +96 -0
  24. qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py +1 -0
  25. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +68 -0
  26. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +70 -0
  27. qcanvas/ui/course_viewer/tabs/page_tab/__init__.py +1 -0
  28. qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +36 -0
  29. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +74 -0
  30. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +176 -0
  31. qcanvas/ui/course_viewer/tabs/util.py +1 -0
  32. qcanvas/ui/main_ui/course_viewer_container.py +52 -0
  33. qcanvas/ui/main_ui/options/__init__.py +3 -0
  34. qcanvas/ui/main_ui/options/quick_sync_option.py +25 -0
  35. qcanvas/ui/main_ui/options/sync_on_start_option.py +25 -0
  36. qcanvas/ui/main_ui/qcanvas_window.py +192 -0
  37. qcanvas/ui/main_ui/status_bar_progress_display.py +153 -0
  38. qcanvas/ui/memory_tree/__init__.py +2 -0
  39. qcanvas/ui/memory_tree/_tree_memory.py +66 -0
  40. qcanvas/ui/memory_tree/memory_tree_widget.py +133 -0
  41. qcanvas/ui/memory_tree/memory_tree_widget_item.py +19 -0
  42. qcanvas/ui/setup/__init__.py +2 -0
  43. qcanvas/ui/setup/setup_checker.py +17 -0
  44. qcanvas/ui/setup/setup_dialog.py +212 -0
  45. qcanvas/util/__init__.py +2 -0
  46. qcanvas/util/basic_fonts.py +12 -0
  47. qcanvas/util/fe_resource_manager.py +23 -0
  48. qcanvas/util/html_cleaner.py +25 -0
  49. qcanvas/util/layouts.py +52 -0
  50. qcanvas/util/logs.py +6 -0
  51. qcanvas/util/paths.py +41 -0
  52. qcanvas/util/settings/__init__.py +9 -0
  53. qcanvas/util/settings/_client_settings.py +29 -0
  54. qcanvas/util/settings/_mapped_setting.py +63 -0
  55. qcanvas/util/settings/_ui_settings.py +34 -0
  56. qcanvas/util/ui_tools.py +41 -0
  57. qcanvas/util/url_checker.py +13 -0
  58. qcanvas-1.0.3.post1.dist-info/METADATA +59 -0
  59. qcanvas-1.0.3.post1.dist-info/RECORD +64 -0
  60. {qcanvas-0.0.5.7a0.dist-info → qcanvas-1.0.3.post1.dist-info}/WHEEL +1 -1
  61. qcanvas-1.0.3.post1.dist-info/entry_points.txt +3 -0
  62. qcanvas/__main__.py +0 -155
  63. qcanvas/db/__init__.py +0 -5
  64. qcanvas/db/database.py +0 -338
  65. qcanvas/db/db_converter_helper.py +0 -81
  66. qcanvas/net/canvas/__init__.py +0 -2
  67. qcanvas/net/canvas/canvas_client.py +0 -209
  68. qcanvas/net/canvas/legacy_canvas_types.py +0 -124
  69. qcanvas/net/custom_httpx_async_transport.py +0 -34
  70. qcanvas/net/self_authenticating.py +0 -108
  71. qcanvas/queries/__init__.py +0 -4
  72. qcanvas/queries/all_courses.gql +0 -7
  73. qcanvas/queries/all_courses.py +0 -108
  74. qcanvas/queries/canvas_course_data.gql +0 -51
  75. qcanvas/queries/canvas_course_data.py +0 -143
  76. qcanvas/ui/container_item.py +0 -11
  77. qcanvas/ui/main_ui.py +0 -251
  78. qcanvas/ui/menu_bar/__init__.py +0 -0
  79. qcanvas/ui/menu_bar/grouping_preferences_menu.py +0 -61
  80. qcanvas/ui/menu_bar/theme_selection_menu.py +0 -39
  81. qcanvas/ui/setup_dialog.py +0 -190
  82. qcanvas/ui/status_bar_reporter.py +0 -40
  83. qcanvas/ui/viewer/__init__.py +0 -0
  84. qcanvas/ui/viewer/course_list.py +0 -96
  85. qcanvas/ui/viewer/file_list.py +0 -195
  86. qcanvas/ui/viewer/file_view_tab.py +0 -62
  87. qcanvas/ui/viewer/page_list_viewer.py +0 -150
  88. qcanvas/util/app_settings.py +0 -98
  89. qcanvas/util/constants.py +0 -5
  90. qcanvas/util/course_indexer/__init__.py +0 -1
  91. qcanvas/util/course_indexer/conversion_helpers.py +0 -78
  92. qcanvas/util/course_indexer/data_manager.py +0 -447
  93. qcanvas/util/course_indexer/resource_helpers.py +0 -191
  94. qcanvas/util/download_pool.py +0 -58
  95. qcanvas/util/helpers/__init__.py +0 -0
  96. qcanvas/util/helpers/canvas_sanitiser.py +0 -47
  97. qcanvas/util/helpers/file_icon_helper.py +0 -34
  98. qcanvas/util/helpers/qaction_helper.py +0 -25
  99. qcanvas/util/helpers/theme_helper.py +0 -48
  100. qcanvas/util/link_scanner/__init__.py +0 -2
  101. qcanvas/util/link_scanner/canvas_link_scanner.py +0 -41
  102. qcanvas/util/link_scanner/canvas_media_object_scanner.py +0 -60
  103. qcanvas/util/link_scanner/dropbox_scanner.py +0 -68
  104. qcanvas/util/link_scanner/resource_scanner.py +0 -69
  105. qcanvas/util/progress_reporter.py +0 -101
  106. qcanvas/util/self_updater.py +0 -55
  107. qcanvas/util/task_pool.py +0 -253
  108. qcanvas/util/tree_util/__init__.py +0 -3
  109. qcanvas/util/tree_util/expanding_tree.py +0 -165
  110. qcanvas/util/tree_util/model_helpers.py +0 -36
  111. qcanvas/util/tree_util/tree_model.py +0 -85
  112. qcanvas-0.0.5.7a0.dist-info/METADATA +0 -21
  113. qcanvas-0.0.5.7a0.dist-info/RECORD +0 -62
  114. /qcanvas/{net → ui/main_ui}/__init__.py +0 -0
@@ -1,150 +0,0 @@
1
- from abc import abstractmethod
2
- from typing import Sequence
3
-
4
- from PySide6.QtCore import QItemSelection, Slot
5
- from PySide6.QtGui import QStandardItemModel
6
- from PySide6.QtWidgets import QWidget, QTextBrowser, QTreeView, QHBoxLayout
7
- from bs4 import BeautifulSoup
8
-
9
- import qcanvas.db as db
10
- from qcanvas.ui.container_item import ContainerItem
11
- from qcanvas.util.constants import default_assignments_module_names
12
- from qcanvas.util.course_indexer import resource_helpers
13
- from qcanvas.util.helpers import canvas_sanitiser
14
- from qcanvas.util.link_scanner import ResourceScanner
15
-
16
-
17
- class LinkTransformer:
18
- # This is used to indicate that a "link" is actually a resource. The resource id is concatenated to this string.
19
- # It just has to be a valid url or qt does not send it to anchorClicked properly
20
- transformed_url_prefix = "data:,"
21
-
22
- def __init__(self, link_scanners: Sequence[ResourceScanner], files: dict[str, db.Resource]):
23
- self.link_scanners = link_scanners
24
- self.files = files
25
-
26
- def transform_links(self, html: str):
27
- doc = BeautifulSoup(html, 'html.parser')
28
-
29
- for element in doc.find_all(resource_helpers.resource_elements):
30
- for scanner in self.link_scanners:
31
- if scanner.accepts_link(element):
32
- resource_id = f"{scanner.name}:{scanner.extract_id(element)}"
33
- # todo make images actually show on the viewer page if they're downloaded
34
- if resource_id in self.files:
35
- file = self.files[resource_id]
36
-
37
- substitute = doc.new_tag(name="a")
38
- # Put the file id on the end of the url so we don't have to use the scanners to extract an id again..
39
- # The actual url doesn't matter
40
- substitute.attrs["href"] = f"{self.transformed_url_prefix}{file.id}"
41
- substitute.string = f"{file.file_name} ({db.ResourceState.human_readable(file.state)})"
42
-
43
- element.replace_with(substitute)
44
- else:
45
- if element.string is not None:
46
- element.string += " (Failed to index)"
47
-
48
- break
49
-
50
- return str(doc)
51
-
52
-
53
- class PageLikeViewer(QWidget):
54
- def __init__(self, header_name: str, link_transformer: LinkTransformer):
55
- super().__init__()
56
- self.viewer = QTextBrowser()
57
- self.viewer.setOpenLinks(False)
58
-
59
- # todo just use QTreeWidget instead
60
- self.tree = QTreeView()
61
- self.model = QStandardItemModel()
62
- self.header_name = header_name
63
- self.link_transformer = link_transformer
64
-
65
- self.tree.setModel(self.model)
66
- self.tree.selectionModel().selectionChanged.connect(self.on_item_clicked)
67
- self.model.setHorizontalHeaderLabels([self.header_name])
68
-
69
- layout = QHBoxLayout()
70
- layout.addWidget(self.tree)
71
- layout.addWidget(self.viewer)
72
- layout.setStretch(1, 1)
73
- self.setLayout(layout)
74
-
75
- def fill_tree(self, data: db.Course):
76
- self.model.clear()
77
- self.viewer.clear()
78
- self._internal_fill_tree(data)
79
- self.model.setHorizontalHeaderLabels([self.header_name])
80
- self.tree.expandAll()
81
-
82
- def clear(self):
83
- self.model.clear()
84
- self.viewer.clear()
85
- self.model.setHorizontalHeaderLabels([self.header_name])
86
-
87
- @abstractmethod
88
- def _internal_fill_tree(self, data: db.Course):
89
- ...
90
-
91
- @Slot(QItemSelection, QItemSelection)
92
- def on_item_clicked(self, selected: QItemSelection, deselected: QItemSelection):
93
- if len(self.tree.selectedIndexes()) == 0:
94
- return
95
-
96
- node = self.model.itemFromIndex(self.tree.selectedIndexes()[0])
97
-
98
- if isinstance(node, ContainerItem):
99
- item = node.content
100
-
101
- if isinstance(item, db.PageLike):
102
- if item.content is None:
103
- return
104
-
105
- # todo when a file is finished downloading it would be nice if the page was refreshed to show the state properly
106
- html = canvas_sanitiser.remove_stylesheets_from_html(item.content)
107
- self.viewer.setHtml(self.link_transformer.transform_links(html))
108
-
109
-
110
- class PagesViewer(PageLikeViewer):
111
- def __init__(self, link_transformer: LinkTransformer):
112
- super().__init__("Pages", link_transformer)
113
-
114
- def _internal_fill_tree(self, course: db.Course):
115
- root = self.model.invisibleRootItem()
116
-
117
- for module in course.modules:
118
- if module.name.lower() in default_assignments_module_names:
119
- continue
120
-
121
- module_node = ContainerItem(module)
122
- module_node.setSelectable(False)
123
-
124
- for module_item in list[db.ModuleItem](module.items):
125
- module_node.appendRow(ContainerItem(module_item))
126
-
127
- root.appendRow(module_node)
128
-
129
-
130
- class AssignmentsViewer(PageLikeViewer):
131
-
132
- def __init__(self, link_transformer: LinkTransformer):
133
- super().__init__("Putting the ASS in assignments", link_transformer)
134
-
135
- def _internal_fill_tree(self, course: db.Course):
136
- root = self.model.invisibleRootItem()
137
-
138
- default_assessments_module = None
139
-
140
- for module in course.modules:
141
- if module.name.lower() in default_assignments_module_names:
142
- default_assessments_module = module
143
- break
144
-
145
- if default_assessments_module is not None:
146
- for module_item in default_assessments_module.items:
147
- root.appendRow(ContainerItem(module_item))
148
-
149
- for assignment in course.assignments:
150
- root.appendRow(ContainerItem(assignment))
@@ -1,98 +0,0 @@
1
- from typing import TypeVar, Generic
2
-
3
- from PySide6.QtCore import QSettings
4
- from packaging.version import Version
5
-
6
- default_theme = "light"
7
-
8
-
9
- def ensure_theme_is_valid(theme: str) -> str:
10
- """
11
- Ensures that a theme name is valid.
12
- If it is invalid, the default theme ("light") is returned
13
- Parameters
14
- ----------
15
- theme
16
- The theme name
17
- Returns
18
- -------
19
- str
20
- A valid theme name
21
- """
22
- if theme not in ["auto", "light", "dark", "native"]:
23
- return default_theme
24
- else:
25
- return theme
26
-
27
-
28
- T = TypeVar("T")
29
-
30
-
31
- class MappedSetting(Generic[T]):
32
- """
33
- Acts as a proxy for a named value in a QSettings object.
34
- Stores the value in memory when initialised and updates it accordingly, to protect it from changes on disk.
35
- """
36
-
37
- def __init__(self, settings_object: QSettings, setting_name: str, default: T | None = None):
38
- self.settings_object = settings_object
39
- self.setting_name = setting_name
40
- self.value = self.settings_object.value(self.setting_name, default)
41
-
42
- def __get__(self, instance, owner):
43
- return self.value
44
-
45
- def __set__(self, instance, value) -> None:
46
- self.value = value
47
- self.settings_object.setValue(self.setting_name, value)
48
-
49
-
50
- class ThemeSetting(MappedSetting):
51
- def __init__(self, settings_object: QSettings):
52
- super().__init__(settings_object, "theme", default_theme)
53
-
54
- def __get__(self, instance, owner):
55
- return ensure_theme_is_valid(super().__get__(instance, owner))
56
-
57
- def __set__(self, instance, value):
58
- super().__set__(instance, ensure_theme_is_valid(value))
59
-
60
-
61
- class _AppSettings:
62
- """
63
- Attributes
64
- ----------
65
- settings : QSettings
66
- Primary settings map for client settings
67
- auxiliary : QSettings
68
- Secondary settings map for settings which aren't related to canvas/panopto client functionality
69
- ignored_update
70
- If there is an update available and the user chooses not to update, then that version will be stored in here and
71
- the user will not be asked to update to that version again
72
- geometry
73
- Used to restore the main window position when it is re-opened
74
- window_state
75
- Used to restore the main window position when it is re-opened
76
- theme
77
- The theme of the app
78
- canvas_url
79
- The canvas url
80
- api_key
81
- The api key for canvas
82
- """
83
-
84
- settings = QSettings("QCanvas", "client")
85
- auxiliary = QSettings("QCanvas", "ui")
86
-
87
- canvas_url: MappedSetting[str] = MappedSetting(settings, "canvas_url")
88
- panopto_url: MappedSetting[str] = MappedSetting(settings, "panopto_url")
89
- api_key: MappedSetting[str] = MappedSetting(settings, "api_key")
90
-
91
- ignored_update: MappedSetting[Version] = MappedSetting(auxiliary, "ignored_update")
92
- theme: ThemeSetting = ThemeSetting(auxiliary)
93
- geometry = MappedSetting(auxiliary, "geometry")
94
- window_state = MappedSetting(auxiliary, "window_state")
95
-
96
-
97
- # Global _AppSettings instance
98
- settings = _AppSettings()
qcanvas/util/constants.py DELETED
@@ -1,5 +0,0 @@
1
- default_assignments_module_names = ["assessments", "assessment"]
2
- app_name = "QCanvas"
3
- package_name = "qcanvas"
4
- # Passed back to the launcher script to indicate that the program should be restarted (after an update)
5
- updated_and_needs_restart_return_code = 144
@@ -1 +0,0 @@
1
- from .data_manager import DataManager
@@ -1,78 +0,0 @@
1
- from typing import Sequence
2
-
3
- from sqlalchemy.ext.asyncio import AsyncSession
4
-
5
- import qcanvas.db as db
6
- import qcanvas.queries as queries
7
- from qcanvas.util.helpers import canvas_sanitiser
8
-
9
-
10
- async def create_assignments(g_course: queries.Course, session: AsyncSession) -> Sequence[db.Assignment]:
11
- """
12
- Puts assignments for a course into the database
13
- """
14
- assignments = []
15
-
16
- for g_assignment in g_course.assignments_connection.nodes:
17
- assignment = await session.get(db.Assignment, g_assignment.q_id)
18
-
19
- if assignment is None:
20
- assignment = db.Assignment()
21
- assignment.id = g_assignment.q_id
22
- assignment.course_id = g_course.m_id
23
- session.add(assignment)
24
- # todo related to scanning old modulepages, but assignment content is already present in the query result so no more requests need to be made
25
- elif g_assignment.updated_at.replace(tzinfo=None) <= assignment.updated_at:
26
- continue
27
-
28
- assignment.name = canvas_sanitiser.remove_garbage_from_title(g_assignment.name)
29
- assignment.description = g_assignment.description
30
- assignment.created_at = g_assignment.created_at
31
- assignment.updated_at = g_assignment.updated_at
32
- assignment.due_at = g_assignment.due_at
33
- assignment.position = g_assignment.position
34
-
35
- assignments.append(assignment)
36
-
37
- return assignments
38
-
39
-
40
- async def create_modules(g_course: queries.Course, session: AsyncSession):
41
- """
42
- Creates modules for a course and puts them in the database
43
- """
44
- for g_module in g_course.modules_connection.nodes:
45
- module = await session.get(db.Module, g_module.q_id)
46
-
47
- if module is None:
48
- module = db.Module()
49
- module.id = g_module.q_id
50
- module.course_id = g_course.m_id
51
- session.add(module)
52
-
53
- module.name = canvas_sanitiser.remove_garbage_from_title(g_module.name)
54
-
55
-
56
- async def create_course(g_course: queries.Course, session: AsyncSession, term: db.Term):
57
- """
58
- Creates course entries in the database
59
- """
60
- course = await session.get(db.Course, g_course.m_id)
61
- if course is None:
62
- course = db.Course()
63
- course.id = g_course.m_id
64
- course.preferences = db.CoursePreferences()
65
- session.add(course)
66
-
67
- course.name = canvas_sanitiser.remove_garbage_from_title(g_course.name)
68
- course.term = term
69
-
70
-
71
- async def create_term(g_course: queries.Course, session) -> db.Term:
72
- term = await session.get(db.Term, g_course.term.q_id)
73
-
74
- if term is None:
75
- term = db.convert_term(g_course.term)
76
- session.add(term)
77
-
78
- return term