qcanvas 0.0.5.7a0__py3-none-any.whl → 1.0.3.post0__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.
- qcanvas/app_start/__init__.py +47 -0
- qcanvas/backend_connectors/__init__.py +2 -0
- qcanvas/backend_connectors/frontend_resource_manager.py +63 -0
- qcanvas/backend_connectors/qcanvas_task_master.py +28 -0
- qcanvas/icons/__init__.py +6 -0
- qcanvas/icons/file-download-failed.svg +6 -0
- qcanvas/icons/file-downloaded.svg +6 -0
- qcanvas/icons/file-not-downloaded.svg +6 -0
- qcanvas/icons/file-unknown.svg +6 -0
- qcanvas/icons/icons.qrc +4 -0
- qcanvas/icons/main_icon.svg +7 -7
- qcanvas/icons/rc_icons.py +580 -214
- qcanvas/icons/sync.svg +6 -6
- qcanvas/run.py +29 -0
- qcanvas/ui/course_viewer/__init__.py +2 -0
- qcanvas/ui/course_viewer/content_tree.py +123 -0
- qcanvas/ui/course_viewer/course_tree.py +93 -0
- qcanvas/ui/course_viewer/course_viewer.py +62 -0
- qcanvas/ui/course_viewer/tabs/__init__.py +3 -0
- qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py +1 -0
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +168 -0
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +104 -0
- qcanvas/ui/course_viewer/tabs/content_tab.py +96 -0
- qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py +1 -0
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +68 -0
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +70 -0
- qcanvas/ui/course_viewer/tabs/page_tab/__init__.py +1 -0
- qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +36 -0
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +74 -0
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +176 -0
- qcanvas/ui/course_viewer/tabs/util.py +1 -0
- qcanvas/ui/main_ui/course_viewer_container.py +52 -0
- qcanvas/ui/main_ui/options/__init__.py +3 -0
- qcanvas/ui/main_ui/options/quick_sync_option.py +25 -0
- qcanvas/ui/main_ui/options/sync_on_start_option.py +25 -0
- qcanvas/ui/main_ui/qcanvas_window.py +192 -0
- qcanvas/ui/main_ui/status_bar_progress_display.py +153 -0
- qcanvas/ui/memory_tree/__init__.py +2 -0
- qcanvas/ui/memory_tree/_tree_memory.py +66 -0
- qcanvas/ui/memory_tree/memory_tree_widget.py +133 -0
- qcanvas/ui/memory_tree/memory_tree_widget_item.py +19 -0
- qcanvas/ui/setup/__init__.py +2 -0
- qcanvas/ui/setup/setup_checker.py +17 -0
- qcanvas/ui/setup/setup_dialog.py +212 -0
- qcanvas/util/__init__.py +2 -0
- qcanvas/util/basic_fonts.py +12 -0
- qcanvas/util/fe_resource_manager.py +23 -0
- qcanvas/util/html_cleaner.py +25 -0
- qcanvas/util/layouts.py +52 -0
- qcanvas/util/logs.py +6 -0
- qcanvas/util/paths.py +41 -0
- qcanvas/util/settings/__init__.py +9 -0
- qcanvas/util/settings/_client_settings.py +29 -0
- qcanvas/util/settings/_mapped_setting.py +63 -0
- qcanvas/util/settings/_ui_settings.py +34 -0
- qcanvas/util/ui_tools.py +41 -0
- qcanvas/util/url_checker.py +13 -0
- qcanvas-1.0.3.post0.dist-info/METADATA +61 -0
- qcanvas-1.0.3.post0.dist-info/RECORD +64 -0
- {qcanvas-0.0.5.7a0.dist-info → qcanvas-1.0.3.post0.dist-info}/WHEEL +1 -1
- qcanvas-1.0.3.post0.dist-info/entry_points.txt +3 -0
- qcanvas/__main__.py +0 -155
- qcanvas/db/__init__.py +0 -5
- qcanvas/db/database.py +0 -338
- qcanvas/db/db_converter_helper.py +0 -81
- qcanvas/net/canvas/__init__.py +0 -2
- qcanvas/net/canvas/canvas_client.py +0 -209
- qcanvas/net/canvas/legacy_canvas_types.py +0 -124
- qcanvas/net/custom_httpx_async_transport.py +0 -34
- qcanvas/net/self_authenticating.py +0 -108
- qcanvas/queries/__init__.py +0 -4
- qcanvas/queries/all_courses.gql +0 -7
- qcanvas/queries/all_courses.py +0 -108
- qcanvas/queries/canvas_course_data.gql +0 -51
- qcanvas/queries/canvas_course_data.py +0 -143
- qcanvas/ui/container_item.py +0 -11
- qcanvas/ui/main_ui.py +0 -251
- qcanvas/ui/menu_bar/__init__.py +0 -0
- qcanvas/ui/menu_bar/grouping_preferences_menu.py +0 -61
- qcanvas/ui/menu_bar/theme_selection_menu.py +0 -39
- qcanvas/ui/setup_dialog.py +0 -190
- qcanvas/ui/status_bar_reporter.py +0 -40
- qcanvas/ui/viewer/__init__.py +0 -0
- qcanvas/ui/viewer/course_list.py +0 -96
- qcanvas/ui/viewer/file_list.py +0 -195
- qcanvas/ui/viewer/file_view_tab.py +0 -62
- qcanvas/ui/viewer/page_list_viewer.py +0 -150
- qcanvas/util/app_settings.py +0 -98
- qcanvas/util/constants.py +0 -5
- qcanvas/util/course_indexer/__init__.py +0 -1
- qcanvas/util/course_indexer/conversion_helpers.py +0 -78
- qcanvas/util/course_indexer/data_manager.py +0 -447
- qcanvas/util/course_indexer/resource_helpers.py +0 -191
- qcanvas/util/download_pool.py +0 -58
- qcanvas/util/helpers/__init__.py +0 -0
- qcanvas/util/helpers/canvas_sanitiser.py +0 -47
- qcanvas/util/helpers/file_icon_helper.py +0 -34
- qcanvas/util/helpers/qaction_helper.py +0 -25
- qcanvas/util/helpers/theme_helper.py +0 -48
- qcanvas/util/link_scanner/__init__.py +0 -2
- qcanvas/util/link_scanner/canvas_link_scanner.py +0 -41
- qcanvas/util/link_scanner/canvas_media_object_scanner.py +0 -60
- qcanvas/util/link_scanner/dropbox_scanner.py +0 -68
- qcanvas/util/link_scanner/resource_scanner.py +0 -69
- qcanvas/util/progress_reporter.py +0 -101
- qcanvas/util/self_updater.py +0 -55
- qcanvas/util/task_pool.py +0 -253
- qcanvas/util/tree_util/__init__.py +0 -3
- qcanvas/util/tree_util/expanding_tree.py +0 -165
- qcanvas/util/tree_util/model_helpers.py +0 -36
- qcanvas/util/tree_util/tree_model.py +0 -85
- qcanvas-0.0.5.7a0.dist-info/METADATA +0 -21
- qcanvas-0.0.5.7a0.dist-info/RECORD +0 -62
- /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))
|
qcanvas/util/app_settings.py
DELETED
|
@@ -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 +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
|