Skip to content

Commit 5260ba3

Browse files
feat: fully typed primary keys for StagedContent model
1 parent 87795e1 commit 5260ba3

7 files changed

Lines changed: 40 additions & 19 deletions

File tree

cms/djangoapps/contentstore/helpers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Only Studio-specfic helper functions should be added here.
77
Platform-wide Python APIs should be added to an appropriate api.py file instead.
88
"""
9+
910
from __future__ import annotations
1011

1112
import json
@@ -33,6 +34,7 @@
3334
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
3435
from cms.lib.xblock.upstream_sync import UpstreamLink, UpstreamLinkException
3536
from cms.lib.xblock.upstream_sync_block import fetch_customizable_fields_from_block
37+
from openedx.core.djangoapps.content_staging.api import StagedContentID
3638
from openedx.core.djangoapps.content_staging.data import LIBRARY_SYNC_PURPOSE
3739
from openedx.core.djangoapps.content_tagging.types import TagValuesByObjectIdDict
3840
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
@@ -312,7 +314,7 @@ def _rewrite_static_asset_references(downstream_xblock: XBlock, substitutions: d
312314

313315

314316
def _insert_static_files_into_downstream_xblock(
315-
downstream_xblock: XBlock, staged_content_id: int, request
317+
downstream_xblock: XBlock, staged_content_id: StagedContentID, request
316318
) -> StaticFileNotices:
317319
"""
318320
Gets static files from staged content, and inserts them into the downstream XBlock.
@@ -635,7 +637,7 @@ def _import_xml_node_to_parent(
635637

636638
def _import_files_into_course(
637639
course_key: CourseKey,
638-
staged_content_id: int,
640+
staged_content_id: StagedContentID,
639641
static_files: list[content_staging_api.StagedContentFileData],
640642
usage_key: UsageKey,
641643
) -> tuple[StaticFileNotices, dict[str, str]]:
@@ -700,7 +702,7 @@ def _import_files_into_course(
700702

701703
def _import_file_into_course(
702704
course_key: CourseKey,
703-
staged_content_id: int,
705+
staged_content_id: StagedContentID,
704706
file_data_obj: content_staging_api.StagedContentFileData,
705707
usage_key: UsageKey,
706708
) -> tuple[bool | None, dict]:
@@ -760,7 +762,7 @@ def _import_file_into_course(
760762

761763
def _import_transcripts(
762764
block: XBlock,
763-
staged_content_id: int,
765+
staged_content_id: StagedContentID,
764766
static_files: list[content_staging_api.StagedContentFileData],
765767
):
766768
"""

openedx/core/djangoapps/content_libraries/api/blocks.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
)
4242
from xblock.core import XBlock
4343

44+
from openedx.core.djangoapps.content_staging.data import StagedContentID
4445
from openedx.core.djangoapps.xblock.api import (
4546
get_component_from_usage_key,
4647
get_xblock_app_config,
@@ -382,7 +383,7 @@ def _import_staged_block(
382383
library_key: LibraryLocatorV2,
383384
source_context_key: LearningContextKey,
384385
user,
385-
staged_content_id: int,
386+
staged_content_id: StagedContentID,
386387
staged_content_files: list[StagedContentFileData],
387388
now: datetime,
388389
) -> LibraryXBlockMetadata:
@@ -517,7 +518,7 @@ def _import_staged_block_as_container(
517518
library_key: LibraryLocatorV2,
518519
source_context_key: LearningContextKey,
519520
user,
520-
staged_content_id: int,
521+
staged_content_id: StagedContentID,
521522
staged_content_files: list[StagedContentFileData],
522523
now: datetime,
523524
*,

openedx/core/djangoapps/content_staging/api.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@
1919
from xmodule.contentstore.content import StaticContent
2020
from xmodule.contentstore.django import contentstore
2121

22-
from .data import CLIPBOARD_PURPOSE, StagedContentData, StagedContentFileData, StagedContentStatus, UserClipboardData
22+
from .data import (
23+
CLIPBOARD_PURPOSE,
24+
StagedContentData,
25+
StagedContentFileData,
26+
StagedContentID,
27+
StagedContentStatus,
28+
UserClipboardData,
29+
)
2330
from .models import StagedContent as _StagedContent
2431
from .models import StagedContentFile as _StagedContentFile
2532
from .models import UserClipboard as _UserClipboard
@@ -315,26 +322,26 @@ def _user_clipboard_model_to_data(clipboard: _UserClipboard) -> UserClipboardDat
315322
)
316323

317324

318-
def get_staged_content_olx(staged_content_id: int) -> str | None:
325+
def get_staged_content_olx(staged_content_id: StagedContentID) -> str | None:
319326
"""
320327
Get the OLX (as a string) for the given StagedContent.
321328
322329
Does not check permissions!
323330
"""
324331
try:
325-
sc = _StagedContent.objects.get(pk=staged_content_id)
332+
sc = _StagedContent.objects.get(id=staged_content_id)
326333
return sc.olx
327334
except _StagedContent.DoesNotExist:
328335
return None
329336

330337

331-
def get_staged_content_static_files(staged_content_id: int) -> list[StagedContentFileData]:
338+
def get_staged_content_static_files(staged_content_id: StagedContentID) -> list[StagedContentFileData]:
332339
"""
333340
Get the filenames and metadata for any static files used by the given staged content.
334341
335342
Does not check permissions!
336343
"""
337-
sc = _StagedContent.objects.get(pk=staged_content_id)
344+
sc = _StagedContent.objects.get(id=staged_content_id)
338345

339346
def str_to_key(source_key_str: str):
340347
if not source_key_str:
@@ -360,13 +367,13 @@ def str_to_key(source_key_str: str):
360367
]
361368

362369

363-
def get_staged_content_static_file_data(staged_content_id: int, filename: str) -> bytes | None:
370+
def get_staged_content_static_file_data(staged_content_id: StagedContentID, filename: str) -> bytes | None:
364371
"""
365372
Get the data for the static asset associated with the given staged content.
366373
367374
Does not check permissions!
368375
"""
369-
sc = _StagedContent.objects.get(pk=staged_content_id)
376+
sc = _StagedContent.objects.get(id=staged_content_id)
370377
file_data_obj = sc.files.filter(filename=filename).first()
371378
if file_data_obj:
372379
return file_data_obj.data_file.open().read()

openedx/core/djangoapps/content_staging/data.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
from datetime import datetime
7+
from typing import NewType
78

89
from attrs import field, frozen, validators
910
from django.db.models import TextChoices
@@ -12,6 +13,9 @@
1213

1314
from openedx.core.djangoapps.content_tagging.api import TagValuesByObjectIdDict
1415

16+
# Typed primary key for the StagedContent model / StagedContentData
17+
StagedContentID = NewType("StagedContentID", int)
18+
1519

1620
class StagedContentStatus(TextChoices):
1721
""" The status of this staged content. """
@@ -42,7 +46,7 @@ class StagedContentData:
4246
4347
(OLX content that isn't part of any course at the moment)
4448
"""
45-
id: int = field(validator=validators.instance_of(int))
49+
id: StagedContentID = field(validator=validators.instance_of(int)) # type: ignore[assignment]
4650
user_id: int = field(validator=validators.instance_of(int))
4751
created: datetime = field(validator=validators.instance_of(datetime))
4852
purpose: str = field(validator=validators.instance_of(str))

openedx/core/djangoapps/content_staging/models.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Models for content staging (and clipboard)
33
"""
4+
45
from __future__ import annotations
56

67
import logging
@@ -11,11 +12,11 @@
1112
from django.utils.translation import gettext_lazy as _
1213
from opaque_keys import InvalidKeyError
1314
from opaque_keys.edx.keys import ContainerKey, LearningContextKey, UsageKey
14-
from openedx_django_lib.fields import MultiCollationTextField, case_insensitive_char_field
15+
from openedx_django_lib.fields import MultiCollationTextField, TypedAutoField, case_insensitive_char_field
1516

1617
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none
1718

18-
from .data import CLIPBOARD_PURPOSE, StagedContentStatus
19+
from .data import CLIPBOARD_PURPOSE, StagedContentID, StagedContentStatus
1920

2021
log = logging.getLogger(__name__)
2122

@@ -42,7 +43,12 @@ class StagedContent(models.Model):
4243
class Meta:
4344
verbose_name_plural = _("Staged Content")
4445

45-
id = models.AutoField(primary_key=True)
46+
type ID = StagedContentID
47+
48+
class IDField(TypedAutoField[ID]):
49+
pass
50+
51+
id = IDField(primary_key=True)
4652
# The user that created and owns this staged content. Only this user can read it.
4753
user = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
4854
created = models.DateTimeField(null=False, auto_now_add=True)

openedx/core/djangoapps/content_staging/tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
@shared_task(base=LoggedTask)
1919
@set_code_owner_attribute
20-
def delete_expired_clipboards(staged_content_ids: list[int]):
20+
def delete_expired_clipboards(staged_content_ids: list[StagedContent.ID]):
2121
"""
2222
A Celery task to delete StagedContent clipboard entries that are no longer
2323
relevant.

openedx/core/djangoapps/content_staging/tests/test_clipboard.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Tests for the clipboard functionality
44
"""
55
from textwrap import dedent
6+
from typing import cast
67
from xml.etree import ElementTree
78

89
from rest_framework.test import APIClient
@@ -294,7 +295,7 @@ def test_copy_static_assets(self):
294295
# Validate the response:
295296
assert response.status_code == 200
296297
response_data = response.json()
297-
staged_content_id = response_data["content"]["id"]
298+
staged_content_id = cast(python_api.StagedContentID, response_data["content"]["id"])
298299
olx_str = python_api.get_staged_content_olx(staged_content_id)
299300
assert '<img src="/static/foo_bar.jpg" />' in olx_str
300301
static_assets = python_api.get_staged_content_static_files(staged_content_id)

0 commit comments

Comments
 (0)