Content Models#
This is where we expose a small handful of models and model mixins that we want callers to extend or make foreign keys to. Callers importing this module should never instantiate any of the models themselves–there are API functions in authoring.py to create and modify data models in a way that keeps those models consistent.
- class openedx_content.models_api.Collection(*args, **kwargs)#
Bases:
ModelRepresents a collection of library components
- Parameters:
id (AutoField) – Primary key: Id
collection_code (MultiCollationCharField) –
Collection code
Every collection is uniquely and permanently identified within its learning package by a ‘code’ that is set during creation. Both will appear in the collection’s opaque key: e.g.
lib-collection:{org_code}:{library_code}:{collection_code}is the opaque key for a library collection.title (MultiCollationCharField) – Title. The title of the collection.
description (MultiCollationTextField) – Description. Provides extra information for the user about this collection.
enabled (BooleanField) – Enabled. Disabled collections are “soft deleted”, and should be re-enabled before use, or be deleted.
created (DateTimeField) – Created
modified (DateTimeField) – Modified
Relationship fields:
- Parameters:
learning_package (
ForeignKeytoLearningPackage) –Learning package (related name:
collection)Each collection belongs to a
LearningPackage.created_by (
ForeignKeytoUser) – Created by (related name:collection)entities (
ManyToManyFieldtoPublishableEntity) – Entities (related name:collections)
Reverse relationships:
- Parameters:
collectionpublishableentity (Reverse
ForeignKeyfromCollectionPublishableEntity) – All collection publishable entitys of this collection (related name ofcollection)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- collection_code#
MultiCollationCharFieldCollection code
Every collection is uniquely and permanently identified within its learning package by a ‘code’ that is set during creation. Both will appear in the collection’s opaque key: e.g.
lib-collection:{org_code}:{library_code}:{collection_code}is the opaque key for a library collection.- Type:
Type
- learning_package#
-
Learning package (related name:
collection)Each collection belongs to a
LearningPackage.- Type:
Type
- class openedx_content.models_api.CollectionPublishableEntity(*args, **kwargs)#
Bases:
ModelCollection→PublishableEntityassociation.- Parameters:
id (BigAutoField) – Primary key: ID
created (DateTimeField) – Created
Relationship fields:
- Parameters:
collection (
ForeignKeytoCollection) – Collection (related name:collectionpublishableentity)entity (
ForeignKeytoPublishableEntity) – Entity (related name:collectionpublishableentity)created_by (
ForeignKeytoUser) – Created by (related name:collectionpublishableentity)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.Component(*args, **kwargs)#
Bases:
PublishableEntityMixinThis represents any Component that has ever existed in a
LearningPackage.What is a Component#
A
Componentis an entity like a Problem or Video. It has enough information to identify itself and determine what the handler should be (e.g. XBlock Problem), but little beyond that.A
Componentwill have manyComponentVersionobjects over time, and most metadata is associated with theComponentVersionmodel and theMediathatComponentVersionobjects are associated with.A
Componentbelongs to exactly oneLearningPackage.A
Componentis 1:1 withPublishableEntityand has matching primary key values. More specifically,Component.pkmaps toComponent.publishable_entity_id, and any place where the Publishing API module expects to get aPublishableEntity.id, you can use aComponent.pkinstead.Identifiers#
Components have a
publishable_entityOneToOneFieldto thepublishingapp’sPublishableEntityfield, and it uses this as its primary key. Please seePublishableEntity’s docstring for how you should use itsuuidandkeyfields.State Consistency#
The
keyfield on Component’spublishable_entityis derived from thecomponent_typeandcomponent_codefields in this model. We don’t support changing the keys yet, but if we do, those values need to be kept in sync.How build on this model#
Make a foreign key to the
Componentmodel when you need a stable reference that will exist for as long as theLearningPackageitself exists.- param component_code:
Component code
component_codeis an identifier that is local to thelearning_packageandcomponent_type. Thepublishable.entity_refis derived fromcomponent_typeandcomponent_code.- type component_code:
~openedx_django_lib.fields.MultiCollationCharField
Relationship fields:
- param publishable_entity:
Primary key: Publishable entity (related name:
component)- type publishable_entity:
- param learning_package:
Learning package (related name:
component)This foreign key is technically redundant because we’re already locked to a single
LearningPackagethrough ourpublishable_entityrelation. However, having this foreign key directly allows us to make indexes that efficiently query by otherComponentfields within a givenLearningPackage, which is going to be a common use case (and we can’t make a compound index using columns from different tables).- type learning_package:
- param component_type:
Component type (related name:
component)What kind of
Componentare we? This will usually represent a specific XBlock block_type, but we want it to be more flexible in the long term.- type component_type:
Reverse relationships:
- param versions:
All versions of this Component (related name of
component)- type versions:
Reverse
ForeignKeyfromComponentVersion
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- component_code#
MultiCollationCharFieldComponent code
component_codeis an identifier that is local to thelearning_packageandcomponent_type. Thepublishable.entity_refis derived fromcomponent_typeandcomponent_code.- Type:
Type
- component_type#
-
Component type (related name:
component)What kind of
Componentare we? This will usually represent a specific XBlock block_type, but we want it to be more flexible in the long term.- Type:
Type
- learning_package#
-
Learning package (related name:
component)This foreign key is technically redundant because we’re already locked to a single
LearningPackagethrough ourpublishable_entityrelation. However, having this foreign key directly allows us to make indexes that efficiently query by otherComponentfields within a givenLearningPackage, which is going to be a common use case (and we can’t make a compound index using columns from different tables).- Type:
Type
- property pk#
Mark the .pk attribute as deprecated
- class openedx_content.models_api.ComponentType(*args, **kwargs)#
Bases:
ModelNormalized representation of a type of Component.
The only namespace being used initially will be
xblock.v1, but we will probably add a few others over time, such as a component type to represent packages of files for things like Files and Uploads orpython_lib.zipfiles.Make a foreign key against this table if you have to set policy based on the type of Components–e.g. marking certain types of XBlocks as approved vs. experimental for use in libraries.
- Parameters:
id (AutoField) –
Primary key: Id
This is intentionally a 4-byte AutoField instead of an 8-byte BigAutoField. We don’t need the app default of 8-bytes for this primary key, but there is just a tiny chance that we’ll use
ComponentTypein a novel, user-customizable way that will require more than 32K entries that a SmallAutoField would allow us.namespace (MultiCollationCharField) –
Namespace
namespaceandnamework together to help figure out whatComponentneeds to handle this data. A namespace is required. The namespace for XBlocks isxblock.v1(to match XBlock’s entrypoint naming scheme).name (MultiCollationCharField) –
Name
nameis a way to help sub-dividenamespaceif that’s convenient. This field cannot be null, but it can be blank if it’s not necessary. For an XBlock, this corresponds to tag, e.g. “video”. It’s also the block_type in the UsageKey.
Reverse relationships:
- Parameters:
component (Reverse
ForeignKeyfromComponent) –All Components of this component type (related name of
component_type)This is technically redundant, since we can get this through
publishable_entity_version.publishable.component, but this is more convenient.
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- id#
-
Primary key: Id
This is intentionally a 4-byte AutoField instead of an 8-byte BigAutoField. We don’t need the app default of 8-bytes for this primary key, but there is just a tiny chance that we’ll use
ComponentTypein a novel, user-customizable way that will require more than 32K entries that a SmallAutoField would allow us.- Type:
Type
- name#
MultiCollationCharFieldName
nameis a way to help sub-dividenamespaceif that’s convenient. This field cannot be null, but it can be blank if it’s not necessary. For an XBlock, this corresponds to tag, e.g. “video”. It’s also the block_type in the UsageKey.- Type:
Type
- class openedx_content.models_api.ComponentVersion(*args, **kwargs)#
Bases:
PublishableEntityVersionMixinA particular version of a
Component.This holds the media using a M:M relationship with
MediaviaComponentVersionMedia.Relationship fields:
- Parameters:
publishable_entity_version (
OneToOneFieldtoPublishableEntityVersion) – Primary key: Publishable entity version (related name:componentversion)component (
ForeignKeytoComponent) –Component (related name:
versions)This is technically redundant, since we can get this through
publishable_entity_version.publishable.component, but this is more convenient.media (
ManyToManyFieldtoMedia) –Media (related name:
component_versions)The media relation holds the actual interesting data associated with this
ComponentVersion.
Reverse relationships:
- Parameters:
componentversionmedia (Reverse
ForeignKeyfromComponentVersionMedia) – All component version medias of this Component Version (related name ofcomponent_version)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- component#
-
Component (related name:
versions)This is technically redundant, since we can get this through
publishable_entity_version.publishable.component, but this is more convenient.- Type:
Type
- media: models.ManyToManyField[Media, ComponentVersionMedia]#
-
Media (related name:
component_versions)The media relation holds the actual interesting data associated with this
ComponentVersion.- Type:
Type
- class openedx_content.models_api.ComponentVersionMedia(*args, **kwargs)#
Bases:
ModelDetermines the
Mediafor a givenComponentVersion.An
ComponentVersionmay be associated with multiple pieces of binary data. For instance, a VideoComponentVersionmight be associated with multiple transcripts in different languages.When
Mediais associated with aComponentVersion, it has apaththat is unique within the context of thatComponentVersion. This is used as a local file-path-like identifier, e.g.static/image.png.Mediais immutable and sharable across multipleComponentVersionobjects.- Parameters:
id (BigAutoField) –
Primary key: ID
This is intentionally a 4-byte AutoField instead of an 8-byte BigAutoField. We don’t need the app default of 8-bytes for this primary key, but there is just a tiny chance that we’ll use
ComponentTypein a novel, user-customizable way that will require more than 32K entries that a SmallAutoField would allow us.path (MultiCollationCharField) –
Path
pathis a local file-path-like identifier for the media within aComponentVersion.
Relationship fields:
- Parameters:
component_version (
ForeignKeytoComponentVersion) – Component version (related name:componentversionmedia)media (
ForeignKeytoMedia) –Media (related name:
componentversionmedia)The media relation holds the actual interesting data associated with this
ComponentVersion.
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- path#
MultiCollationCharFieldPath
pathis a local file-path-like identifier for the media within aComponentVersion.- Type:
Type
- class openedx_content.models_api.Container(*args, **kwargs)#
Bases:
PublishableEntityMixinA
Containeris a type ofPublishableEntitythat holds otherPublishableEntityobjects. For example, a “Unit”Containermight hold severalComponentobjects.For now, all containers have a static “entity list” that defines which containers/components/enities they hold. As we complete the Containers API, we will also add support for dynamic containers which may contain different entities for different learners or at different times.
- Parameters:
container_code (MultiCollationCharField) –
Container code
container_codeis an identifier that is local to thelearning_package. Unlikecomponent_code, it is unique across all container types within the sameLearningPackage.
Relationship fields:
- Parameters:
publishable_entity (
OneToOneFieldtoPublishableEntity) – Primary key: Publishable entity (related name:container)learning_package (
ForeignKeytoLearningPackage) –Learning package (related name:
container)This foreign key is technically redundant because we’re already locked to a single
LearningPackagethrough ourpublishable_entityrelation. However, having this foreign key directly allows us to make indexes that efficiently query by otherContainerfields within a givenLearningPackage.container_type (
ForeignKeytoContainerType) –Container type (related name:
container)The type of the container. Cannot be changed once the container is created.
Reverse relationships:
- Parameters:
versions (Reverse
ForeignKeyfromContainerVersion) – All versions of this container (related name ofcontainer)unit (Reverse
OneToOneFieldfromUnit) – The unit of this container (related name ofcontainer)subsection (Reverse
OneToOneFieldfromSubsection) – The subsection of this container (related name ofcontainer)section (Reverse
OneToOneFieldfromSection) – The section of this container (related name ofcontainer)testcontainer (Reverse
OneToOneFieldfromTestContainer) – The test container of this container (related name ofcontainer)containercontainer (Reverse
OneToOneFieldfromContainerContainer) – The container container of this container (related name ofbase_container)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- container_code#
MultiCollationCharFieldContainer code
container_codeis an identifier that is local to thelearning_package. Unlikecomponent_code, it is unique across all container types within the sameLearningPackage.- Type:
Type
- container_type#
ForeignKeytoContainerTypeContainer type (related name:
container)The type of the container. Cannot be changed once the container is created.
- Type:
Type
- final classmethod get_container_type() ContainerType#
Get the ContainerType for this type of container, auto-creating it if need be.
- learning_package#
-
Learning package (related name:
container)This foreign key is technically redundant because we’re already locked to a single
LearningPackagethrough ourpublishable_entityrelation. However, having this foreign key directly allows us to make indexes that efficiently query by otherContainerfields within a givenLearningPackage.- Type:
Type
- olx_tag_name: str = ''#
The OLX
<tag_name>for XML serialization. Subclasses _may_ override this. Only used in openedx-platform at the moment. We’ll likely have to replace this with something more sophisticated.
- property pk#
Mark the .pk attribute as deprecated
- final static reset_cache() None#
Helper for test cases that truncate the database between tests. Call this to delete the cache used in get_container_type(), which will be invalid after the ContainerType table is truncated.
- static subclass_for_type_code(type_code: str) type[Container]#
Get the subclass for the specified container type_code.
- classmethod validate_entity(entity: PublishableEntity) None#
Check if the given entity is allowed as a child of this Container type
Subclasses should raise ValidationError if “entity” is invalid.
- class openedx_content.models_api.ContainerVersion(*args, **kwargs)#
Bases:
PublishableEntityVersionMixinA version of a
Container.By convention, we would only want to create new versions when the
Containeritself changes, and not when theContainer’s child elements change. For example:Something was added to the
Container.We re-ordered the rows in the container.
Something was removed to the container.
The
Container’s metadata changed, e.g. the title.We pin to different versions of the
Container.
The last looks a bit odd, but it’s because how we’ve defined the Unit has changed if we decide to explicitly pin a set of versions for the children, and then later change our minds and move to a different set. It also just makes things easier to reason about if we say that
entity_listnever changes for a givenContainerVersion.Relationship fields:
- Parameters:
publishable_entity_version (
OneToOneFieldtoPublishableEntityVersion) – Primary key: Publishable entity version (related name:containerversion)container (
ForeignKeytoContainer) – Container (related name:versions)entity_list (
ForeignKeytoEntityList) –Entity list (related name:
container_versions)The list of entities (frozen and/or unfrozen) in this container.
Reverse relationships:
- Parameters:
unitversion (Reverse
OneToOneFieldfromUnitVersion) – The unit version of this container version (related name ofcontainer_version)subsectionversion (Reverse
OneToOneFieldfromSubsectionVersion) – The subsection version of this container version (related name ofcontainer_version)sectionversion (Reverse
OneToOneFieldfromSectionVersion) – The section version of this container version (related name ofcontainer_version)testcontainerversion (Reverse
OneToOneFieldfromTestContainerVersion) – The test container version of this container version (related name ofcontainer_version)containercontainerversion (Reverse
OneToOneFieldfromContainerContainerVersion) – The container container version of this container version (related name ofcontainer_version)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- clean()#
Validate this model before saving. Not called normally, but will be called if anything is edited via a ModelForm like the Django admin.
- entity_list#
-
Entity list (related name:
container_versions)The list of entities (frozen and/or unfrozen) in this container.
- Type:
Type
- class openedx_content.models_api.Draft(*args, **kwargs)#
Bases:
ModelFind the active draft version of an entity (usually most recently created).
This model mostly only exists to allow us to join against a bunch of
PublishableEntityobjects at once and get all their latest drafts. You might use this together withPublishedin order to see which Drafts haven’t been published yet.A
Draftentry should be created whenever a newPublishableEntityVersionis created. This means there are three possible states:No
Draftentry for aPublishableEntity: This means aPublishableEntitywas created, but noPublishableEntityVersionwas ever made for it, so there was never a Draft version.A
Draftentry exists and points to aPublishableEntityVersion: This is the most common state.A
Draftentry exists and points to a null version: This means a version used to be the draft, but it’s been functionally “deleted”. The versions still exist in our history, but we’re done using it.
It would have saved a little space to add this data to the
Publishedmodel (and possibly call the combined model something else). Split Modulestore did this with itsactive_versionstable. I keep it separate here to get a better separation of lifecycle events: i.e. this table only changes when drafts are updated, not when publishing happens. ThePublishedmodel only changes when something is published.Relationship fields:
- Parameters:
entity (
OneToOneFieldtoPublishableEntity) –Primary key: Entity (related name:
draft)If we’re removing a
PublishableEntityentirely, also remove theDraftentry for it. This isn’t a normal operation, but can happen if you’re deleting an entireLearningPackage.version (
OneToOneFieldtoPublishableEntityVersion) – Version (related name:draft)draft_log_record (
ForeignKeytoDraftChangeLogRecord) –Draft log record (related name:
draft)Note: this is actually a 1:1 relation in practice, but I’m keeping the definition more consistent with the
Publishedmodel, which has an fkey toPublishLogRecord. UnlikePublishLogRecord, this fkey is a late addition to this data model, so we have to allow null values for the initial migration. But making this nullable also has another advantage, in that it allows us to set thedraft_log_recordto the most recent change while inside abulk_draft_changes_foroperation, and then delete that log record if it is undone in the same bulk operation.
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- class DraftQuerySet(model=None, query=None, using=None, hints=None)#
Bases:
QuerySetCustom QuerySet/Manager so we can chain common queries.
- with_unpublished_changes()#
Draftobjects with versions that are different from what is Published.This will not return
Draftobjects that have unpublished changes in their dependencies. Example: A Unit is published with a Component as one of its child. Then someone modifies the draft of the Component. If both the Unit and the ComponentDraftobjects were part of the queryset, this method would return only the changed Component, and not the Unit. (We can add this as an optional flag later if we want.)
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- draft_log_record#
ForeignKeytoDraftChangeLogRecordDraft log record (related name:
draft)Note: this is actually a 1:1 relation in practice, but I’m keeping the definition more consistent with the
Publishedmodel, which has an fkey toPublishLogRecord. UnlikePublishLogRecord, this fkey is a late addition to this data model, so we have to allow null values for the initial migration. But making this nullable also has another advantage, in that it allows us to set thedraft_log_recordto the most recent change while inside abulk_draft_changes_foroperation, and then delete that log record if it is undone in the same bulk operation.- Type:
Type
- entity#
OneToOneFieldtoPublishableEntityPrimary key: Entity (related name:
draft)If we’re removing a
PublishableEntityentirely, also remove theDraftentry for it. This isn’t a normal operation, but can happen if you’re deleting an entireLearningPackage.- Type:
Type
- class openedx_content.models_api.DraftChangeLog(*args, **kwargs)#
Bases:
ModelThere is one row in this table for every time
Draftobjects are created/modified.There are some operations that affect many
Draftobjects at once, such as discarding changes (i.e. reset to the published versions) or doing an import. These would be represented by oneDraftChangeLogwith manyDraftChangeLogRecordobjects in it—oneDraftChangeLogRecordfor everyPublishableEntitythat was modified.Even if we’re only directly changing the draft version of one
PublishableEntity, we will get multipleDraftChangeLogRecordobjects if changing that entity causes side-effects. See the docstrings forDraftChangeLogRecordandDraftSideEffectfor more details.- Parameters:
id (BigAutoField) – Primary key: ID
uuid (UUIDField) – UUID
changed_at (DateTimeField) – Changed at
Relationship fields:
- Parameters:
learning_package (
ForeignKeytoLearningPackage) – Learning package (related name:draftchangelog)changed_by (
ForeignKeytoUser) – Changed by (related name:draftchangelog)
Reverse relationships:
- Parameters:
records (Reverse
ForeignKeyfromDraftChangeLogRecord) – All records of this Draft Change Log (related name ofdraft_change_log)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.DraftChangeLogRecord(*args, **kwargs)#
Bases:
ModelA single change in the
PublishableEntitythatDraftpoints to.Within a single
DraftChangeLog, there can be only oneDraftChangeLogRecordperPublishableEntity. If aPublishableEntitygoes from v1 → v2 and then v2 → v3 within the sameDraftChangeLog, the expectation is that these will be collapsed into oneDraftChangeLogRecordthat goes from v1 → v3. A singlePublishableEntitymay have manyDraftChangeLogRecordobjects that describe its full draft edit history, but eachDraftChangeLogRecordwill be a part of a differentDraftChangeLog.New
PublishableEntityVersionobjects are created with a monotonically increasingversion_numfor theirPublishableEntity. However, knowing that is not enough to accurately reconstruct how theDraftchanges over time because theDraftdoes not always point to the most recently createdPublishableEntityVersion. We also have the concept of side-effects, where we consider aPublishableEntityto have changed in some way, even if no new version is explicitly created.The following scenarios may occur:
Scenario 1: old_version is None, new_version.version_num = 1
This is the common case when we’re creating the first version for editing.
Scenario 2: old_version.version_num + 1 == new_version.version_num
This is the common case when we’ve made an edit to something, which creates the next version of an entity, which we then point the
Draftat.Scenario 3: old_version.version_num >=1, new_version is None
This is a soft-deletion. We never actually delete a row from the
PublishableEntitymodel, but set its currentDraftversion to be None instead.Scenario 4: old_version.version_num > new_version.version_num
This can happen if we “discard changes”, meaning that we call
reset_drafts_to_published(). The versions we created before resetting don’t get deleted, but theDraftmodel’s pointer to the current version has been reset to match thePublishedmodel.Scenario 5: old_version.version_num + 1 < new_version.version_num
Sometimes we’ll have a gap between the two version numbers that is > 1. This can happen if we make edits (new versions) after we called
reset_drafts_to_published.PublishableEntityVersionobjects are created with a monotonically incrementingversion_numwhich will continue to go up with the next edit, regardless of whetherDraftis pointing to the most recently created version or not. In terms of (old_version,new_version) changes, it could look like this:(None, v1): Initial creation
# Publish happens here, so v1 of this
PublishableEntityis published.(v1, v2): Normal edit in draft
(v2, v3): Normal edit in draft
(v3, v1): Reset to published happened here.
(v1, v4): Normal edit in draft
This could also technically happen if we change the same entity more than once in the the same
bulk_draft_changes_for()context, thereby putting them into the sameDraftChangeLog, which forces us to squash the changes together into oneDraftChangeLogRecord.Scenario 6: old_version is None, new_version > 1
This edge case can happen if we soft-deleted a published entity, and then called
reset_drafts_to_publishedbefore we published that soft-deletion. It would effectively undo our soft-delete because the published version was not yet marked as deleted.Scenario 7: old_version == new_version
This means that the data associated with the
Draftversion of an entity has changed purely as a side-effect of some other entity changing.The main example we have of this are containers. Imagine that we have a Unit that is at v1, and has unpinned references to various Components that are its children. The Unit’s version does not get incremented when the Components are edited, because the Unit container is defined to always get the most recent version of those Components. We would only make a new version of the Unit if we changed the metadata of the Unit itself (e.g. the title), or if we added, removed, or reordered the children.
Yet updating a Component intuitively changes what we think of as the content of the Unit. Users who are working on Units also expect that a change to a Component will be reflected when looking at a Unit’s “last updated” info. The
old_version == new_versionconvention lets us represent that in a useful way because that Unit is a part of the change set represented by aDraftChangeLog, even if its own versioned data hasn’t changed.- Parameters:
id (BigAutoField) – Primary key: ID
dependencies_hash_digest (CharField) –
Dependencies hash digest
The
dependencies_hash_digestis used when the version alone isn’t enough to let us know the full draft state of an entity. This happens any time aDraftversion has dependencies (see thePublishableEntityVersionDependencymodel), because changes in those dependencies will cause changes to the state of theDraft. The main example of this is containers, where changing an unpinned child affects the state of the parent container, even if that container’s definition (and thus version) does not change.If a
Drafthas no dependencies, then its entire state is captured by its version, and thedependencies_hash_digestis blank. (Blank is slightly more convenient for database comparisons than NULL.)Note: There is an equivalent of this field in the
Publishedmodel and the the values may drift away from each other.
Relationship fields:
- Parameters:
draft_change_log (
ForeignKeytoDraftChangeLog) – Draft change log (related name:records)entity (
ForeignKeytoPublishableEntity) –Entity (related name:
draftchangelogrecord)If we’re removing a
PublishableEntityentirely, also remove theDraftentry for it. This isn’t a normal operation, but can happen if you’re deleting an entireLearningPackage.old_version (
ForeignKeytoPublishableEntityVersion) – Old version (related name:+)new_version (
ForeignKeytoPublishableEntityVersion) – New version (related name:draftchangelogrecord)
Reverse relationships:
- Parameters:
draft (Reverse
ForeignKeyfromDraft) – All drafts of this Draft Change Log Record (related name ofdraft_log_record)causes (Reverse
ForeignKeyfromDraftSideEffect) – All causes of this Draft Change Log Record (related name ofcause)affected_by (Reverse
ForeignKeyfromDraftSideEffect) – All affected by of this Draft Change Log Record (related name ofeffect)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- dependencies_hash_digest#
-
Dependencies hash digest
The
dependencies_hash_digestis used when the version alone isn’t enough to let us know the full draft state of an entity. This happens any time aDraftversion has dependencies (see thePublishableEntityVersionDependencymodel), because changes in those dependencies will cause changes to the state of theDraft. The main example of this is containers, where changing an unpinned child affects the state of the parent container, even if that container’s definition (and thus version) does not change.If a
Drafthas no dependencies, then its entire state is captured by its version, and thedependencies_hash_digestis blank. (Blank is slightly more convenient for database comparisons than NULL.)Note: There is an equivalent of this field in the
Publishedmodel and the the values may drift away from each other.- Type:
Type
- class openedx_content.models_api.DraftSideEffect(*args, **kwargs)#
Bases:
ModelModel to track when a change in one
Draftaffects otherDraftobjects.Our first use case for this is that changes involving child components are thought to affect parent Units, even if the parent’s version doesn’t change.
Side-effects are recorded in a collapsed form that only captures one level. So if Components C1 and C2 are both changed and they are part of Unit U1, which is in turn a part of Subsection SS1, then the
DraftSideEffectentries are:(C1, U1) (C2, U1) (U1, SS1)
We do not keep entries for
(C1, SS1)or(C2, SS1). This is to make the model simpler, so we don’t have to differentiate between direct side-effects and transitive side-effects in the model.We will record side-effects on a parent container whenever a child changes, even if the parent container is also changing in the same
DraftChangeLog. The child change is still affecting the parent container, whether the container happens to be changing for other reasons as well. Whether a parent-child relationship exists or not depends on the draft state of the container at the end of abulk_draft_changes_forcontext. To give concrete examples:Setup: A Unit version U1.v1 has defined C1 to be a child. The current draft version of C1 is C1.v1.
Scenario 1: In the a bulk_draft_changes_for context, we edit C1 so that the draft version of C1 is now C1.v2.
Result:
a
DraftChangeLogRecordis created for C1.v1 → C1.v2a
DraftChangeLogRecordis created for U1.v1 → U1.v1a
DraftSideEffectis created with cause (C1.v1 → C1.v2) and effect (U1.v1 → U1.v1). The Unit draft version has not been incremented because the metadata a Unit defines for itself hasn’t been altered, but the Unit has changed in some way because of the side effect of its child being edited.
Scenario 2: In a bulk_draft_changes_for context, we edit C1 so that the draft version of C1 is now C1.v2. In the same context, we edit U1’s metadata so that the draft version of U1 is now U1.v2. U1.v2 still lists C1 as a child entity.
Result:
a
DraftChangeLogRecordis created for C1.v1 → C1.v2a
DraftChangeLogRecordis created for U1.v1 → U1.v2a
DraftSideEffectis created with cause (C1.v1 → C1.v2) and effect (U1.v1 → U1.v2)
Scenario 3: In a bulk_draft_changes_for context, we edit C1 so that the draft version of C1 is now C1.v2. In the same context, we edit U1’s list of children so that C1 is no longer a child of U1.v2.
Result:
a
DraftChangeLogRecordis created for C1.v1 → C1.v2a
DraftChangeLogRecordis created for U1.v1 → U1.v2no SideEffect is created, since changing C1 does not have an impact on the current draft of U1 (U1.v2). A
DraftChangeLogis considered a single atomic operation, so there was never a point at which C1.v1 → C1.v2 affected the draft state of U1.
- Parameters:
id (BigAutoField) – Primary key: ID
Relationship fields:
- Parameters:
cause (
ForeignKeytoDraftChangeLogRecord) – Cause (related name:causes)effect (
ForeignKeytoDraftChangeLogRecord) – Effect (related name:affected_by)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.EntityList(*args, **kwargs)#
Bases:
ModelEntityListis a common structure to hold parent-child relations.EntityListobjects are notPublishableEntityobjects in and of themselves. That’s because sometimes we’ll want the same kind of data structure for things that we dynamically generate for individual students (e.g. Variants).EntityListobjects are anonymous in a sense–they’re pointed to byContainerVersionobjects and other models, rather than being looked up by their own identifiers.- Parameters:
id (BigAutoField) – Primary key: ID
Reverse relationships:
- Parameters:
entitylistrow (Reverse
ForeignKeyfromEntityListRow) – All entity list rows of this entity list (related name ofentity_list)container_versions (Reverse
ForeignKeyfromContainerVersion) – All container versions of this entity list (related name ofentity_list)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- property rows#
Convenience method to iterate rows.
I’d normally make this the reverse lookup name for the
EntityListRow→EntityListforeign key relation, but we already have references toentitylistrow_setin various places, and I thought this would be better than breaking compatibility.
- class openedx_content.models_api.EntityListRow(*args, **kwargs)#
Bases:
ModelEach
EntityListRowpoints to aPublishableEntity, optionally at a specific version.There is a row in this table for each member of an
EntityList. Theorder_numfield is used to determine the order of the members in the list.- Parameters:
id (BigAutoField) – Primary key: ID
order_num (PositiveIntegerField) –
Order num
This ordering should be treated as immutable–if the ordering needs to change, we create a new
EntityListand copy things over.
Relationship fields:
- Parameters:
entity_list (
ForeignKeytoEntityList) –Entity list (related name:
entitylistrow)The list of entities (frozen and/or unfrozen) in this container.
entity (
ForeignKeytoPublishableEntity) –Entity (related name:
entitylistrow)Simple case would use this field with our convention that null versions means “get the latest draft or published as appropriate”. These entities could be Selectors, in which case we’d need to do more work to find the right variant.
entity_version (
ForeignKeytoPublishableEntityVersion) –Entity version (related name:
+)The version references point to the specific
PublishableEntityVersionthat thisEntityListhas for thisPublishableEntityfor both the draft and published states. However, we don’t want to have to create newEntityListevery time that a member is updated, because that would waste a lot of space and make it difficult to figure out when the metadata of something like a Unit actually changed, vs. when its child members were being updated. Doing so could also potentially lead to race conditions when updating multiple layers of containers.So our approach to this is to use a value of
None(null) to represent an unpinned reference to aPublishableEntity. It’s shorthand for “just use the latest draft or published version of this, as appropriate”.
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- entity#
ForeignKeytoPublishableEntityEntity (related name:
entitylistrow)Simple case would use this field with our convention that null versions means “get the latest draft or published as appropriate”. These entities could be Selectors, in which case we’d need to do more work to find the right variant.
- Type:
Type
- entity_version#
ForeignKeytoPublishableEntityVersionEntity version (related name:
+)The version references point to the specific
PublishableEntityVersionthat thisEntityListhas for thisPublishableEntityfor both the draft and published states. However, we don’t want to have to create newEntityListevery time that a member is updated, because that would waste a lot of space and make it difficult to figure out when the metadata of something like a Unit actually changed, vs. when its child members were being updated. Doing so could also potentially lead to race conditions when updating multiple layers of containers.So our approach to this is to use a value of
None(null) to represent an unpinned reference to aPublishableEntity. It’s shorthand for “just use the latest draft or published version of this, as appropriate”.- Type:
Type
- order_num#
-
Order num
This ordering should be treated as immutable–if the ordering needs to change, we create a new
EntityListand copy things over.- Type:
Type
- class openedx_content.models_api.LearningPackage(*args, **kwargs)#
Bases:
ModelTop level container for a grouping of authored content.
Each
PublishableEntitybelongs to exactly oneLearningPackage.- Parameters:
id (IDField) – Primary key: Id
uuid (UUIDField) – UUID
package_ref (MultiCollationCharField) –
Package ref
An opaque reference string for the
LearningPackage.title (MultiCollationCharField) – Title
description (MultiCollationTextField) –
Description
TODO: We should probably defer this field, since many things pull back
LearningPackageas select_related. Usually those relations only care about the UUID andpackage_ref, so maybe it makes sense to separate the model at some point.created (DateTimeField) – Created
updated (DateTimeField) – Updated
Reverse relationships:
- Parameters:
publishable_entities (Reverse
ForeignKeyfromPublishableEntity) – All publishable entities of this Learning Package (related name oflearning_package)draftchangelog (Reverse
ForeignKeyfromDraftChangeLog) – All Draft Change Logs of this Learning Package (related name oflearning_package)publishlog (Reverse
ForeignKeyfromPublishLog) – All Publish Logs of this Learning Package (related name oflearning_package)collection (Reverse
ForeignKeyfromCollection) – All Collections of this Learning Package (related name oflearning_package)media (Reverse
ForeignKeyfromMedia) – All Media of this Learning Package (related name oflearning_package)component (Reverse
ForeignKeyfromComponent) – All Components of this Learning Package (related name oflearning_package)container (Reverse
ForeignKeyfromContainer) – All containers of this Learning Package (related name oflearning_package)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- class IDField(*args, **kwargs)#
Bases:
TypedAutoFieldExplictly declare a 4-byte ID instead of using the app-default 8-byte ID. We do not expect to have more than 2 billion LearningPackages on a given site. Furthermore, many, many things have foreign keys to this model and uniqueness indexes on those foreign keys + their own fields, so the 4 bytes saved will add up over time.
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- description#
MultiCollationTextFieldDescription
TODO: We should probably defer this field, since many things pull back
LearningPackageas select_related. Usually those relations only care about the UUID andpackage_ref, so maybe it makes sense to separate the model at some point.- Type:
Type
- package_ref#
MultiCollationCharFieldPackage ref
An opaque reference string for the
LearningPackage.- Type:
Type
- class openedx_content.models_api.Media(*args, **kwargs)#
Bases:
ModelThis is the most primitive piece of content.
This model serves to lookup, de-duplicate, and store text and files. A piece of
Mediais identified purely by its data, the media type, and theLearningPackageit is associated with. It has no version or file name metadata associated with it. It exists to be a dumb blob of data that higher level models likeComponentVersionobjects can assemble together.In-model Text vs. File#
That being said, the
Mediamodel does have some complexity to accomodate different access patterns that we have in our app. In particular, it can store data in two ways: thetextfield and a file (has_file=True) AMediaobject must use at least one of these methods, but can use both if it’s appropriate.Use the
textfield when:the content is a relatively small (< 50K, usually much less) piece of text
you want to do be able to query across many rows at once
low, predictable latency is important
Use file storage when:
the content is large, or not text-based
you want to be able to serve the file content directly to the browser
The high level tradeoff is that
textwill give you faster access, and file storage will give you a much more affordable and scalable backend. The backend used for files will also eventually allow direct browser download access, whereas thetextfield will not. But again, you can use both at the same time if needed.Association with a LearningPackage#
Mediais associated with a specificLearningPackage. Doing so allows us to more easily query for how much storage space a specificLearningPackageis using, and to clean up unused data.When we use
MediaacrossLearningPackageobjects, we need to copy them. That way, even if the originatingLearningPackageis deleted, it won’t break otherLearningPackageobjects that are making use if it.Media Types, and file duplication#
Mediais almost 1:1 with the files that it pushes to a storage backend, but not quite. The file locations are generated purely as a product of theLearningPackageUUID and theMedia’shash_digest, butMediaalso takes into account themedia_type.For example, say we had a
Mediawith the following data:["hello", "world"]
That is legal syntax for both JSON and YAML. If you want to attach some YAML-specific metadata in a new model, you could make it 1:1 with the
Mediathat matched theapplication/yamlmedia type. The YAML and JSON versions of this data would be two separateMediarows that would share the samehash_digestvalue. If they both stored a file, they would be pointing to the same file location. If they only used thetextfield, then that value would be duplicated across the two separateMediarows.The alternative would have been to associate media types at the level where this data was being added to a
ComponentVersion, but that would have added more complexity. Right now, you could make anImageMedia1:1 model that analyzed images and created metatdata entries for them (dimensions, GPS) without having to understand howComponentVersionobjects work.This is definitely an edge case, and it’s likely the only time collisions like this will happen in practice is with blank files. It also means that using this table to measure disk usage may be slightly inaccurate when used in a
LearningPackagewith collisions–though we expect to use numbers like that mostly to get a broad sense of usage and look for major outliers, rather than for byte-level accuracy (it wouldn’t account for the non-trivial indexing storage costs either).Immutability#
From the outside,
Mediashould appear immutable. Since theMediais looked up by a hash of its data, a change in the data means that we should look up the hash value of that new data and create a newMediaif we don’t find a match.That being said, the
Mediamodel has different ways of storing that data, and that is mutable. We could decide that a certain type ofMediashould be optimized to store its text in the table. Or that a media type that we had previously only stored as text now also needs to be stored on in the file storage backend so that it can be made available to be downloaded. These operations would be done as data migrations.Extensibility#
Third-party apps are encouraged to create models that have a
OneToOneFieldrelationship withMedia. For instance, anImageMediamodel might join 1:1 with allMediathat hasimage/*media types, and provide additional metadata for that data.- param id:
Primary key: Id
We’re going to have many foreign key references from
Mediainto this model, and we don’t need to store those as 8-byteBigAutoField, as is the default for this app. It’s likely that aSmallAutoFieldwould work, but I can just barely imagine using more than 32K Media types if we have a bunch of customvnd.entries, or start tracking suffixes and parameters. Which is how we end up at the 4-byteAutoField.- type id:
~openedx_content.applets.media.models.IDField
- param size:
Size
This is the size of the file in bytes. This can be different than the character length of a text file, since UTF-8 encoding can use anywhere between 1-4 bytes to represent any given character.
- type size:
~django.db.models.PositiveBigIntegerField
- param hash_digest:
Hash digest
This hash value may be calculated using
create_hash_digestfrom theopenedx.lib.fieldsmodule. When storing text, we hash the UTF-8 encoding of that text value, regardless of whether we also write it to a file or not. When storing just a file, we hash the bytes in the file.- type hash_digest:
~django.db.models.CharField
- param has_file:
Has file
Do we have file data stored for this
Mediain our file storage backend? We usehas_fileinstead of aFileFieldbecause it’s more space efficient. The location of aMedia’s file data is derivable from theLearningPackage’s UUID and the hash of theMedia. There’s no need to waste that space to encode it in every row.- type has_file:
~django.db.models.BooleanField
- param text:
Text
The
textfield contains the text representation of theMedia, if it is available. A blank value means means that we are storing text for thisMedia, and that text happens to be an empty string. A null value here means that we are not storing any text here, and theMediaexists only in file form. It is an error fortextto be None andhas_fileto be False, since that would mean we haven’t stored data anywhere at all.- type text:
~openedx_django_lib.fields.MultiCollationTextField
- param created:
Created
This should be manually set so that multiple
Mediarows being set in the same transaction are created with the same timestamp. The timestamp should be UTC.- type created:
~django.db.models.DateTimeField
Relationship fields:
- param learning_package:
Learning package (related name:
media)- type learning_package:
- param media_type:
Media type (related name:
media)What is the
MediaType(a.k.a. MIME type) of this data?- type media_type:
Reverse relationships:
- param component_versions:
All component versions of this Media (related name of
media)- type component_versions:
Reverse
ManyToManyFieldfromComponentVersion- param componentversionmedia:
All component version medias of this Media (related name of
media)- type componentversionmedia:
Reverse
ForeignKeyfromComponentVersionMedia
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- class IDField(*args, **kwargs)#
Bases:
TypedBigAutoField
- MAX_FILE_SIZE = 50000000#
Max size of the file.
- MAX_TEXT_LENGTH = 50000#
50K is our limit for text data, like OLX. This means 50K characters, not bytes. Since UTF-8 encodes characters using as many as 4 bytes, this could be as much as 200K of data if we had nothing but emojis.
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- clean()#
Make sure we’re actually storing something.
If this
Mediahas neither a file or text data associated with it, it’s in a broken/useless state and shouldn’t be saved.
- created#
-
Created
This should be manually set so that multiple
Mediarows being set in the same transaction are created with the same timestamp. The timestamp should be UTC.- Type:
Type
- has_file#
-
Has file
Do we have file data stored for this
Mediain our file storage backend? We usehas_fileinstead of aFileFieldbecause it’s more space efficient. The location of aMedia’s file data is derivable from theLearningPackage’s UUID and the hash of theMedia. There’s no need to waste that space to encode it in every row.- Type:
Type
- hash_digest#
-
Hash digest
This hash value may be calculated using
create_hash_digestfrom theopenedx.lib.fieldsmodule. When storing text, we hash the UTF-8 encoding of that text value, regardless of whether we also write it to a file or not. When storing just a file, we hash the bytes in the file.- Type:
Type
- media_type#
-
Media type (related name:
media)What is the
MediaType(a.k.a. MIME type) of this data?- Type:
Type
- property mime_type: str#
The IANA media type (a.k.a. MIME type) of the
Media, in string form.MIME types reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
- os_path()#
The full OS path for the underlying file for this
Media.This will not be supported by all
Storageclass types.This will return
Noneif there is no backing file (has_file=False).
- property path#
Logical path at which this content is stored (or would be stored).
This path is relative to
OPENEDX_LEARNING['MEDIA']configured storage root. This file may not exist becausehas_file=False, or because we haven’t written the file yet (this is the method we call when trying to figure out where the file should go).For historical reasons (and backwards compatibility), the prefix for this path is
content/and notmedia/.
- read_file() File#
Get a
Fileobject that has been open for reading.We intentionally don’t expose an
open()call where callers can open this file in write mode. Writing aMediafile should happen at most once, and the logic is not obvious (seewrite_file()).At the end of the day, the caller can close the returned
Fileand reopen it in whatever mode they want, but we’re trying to gently discourage that kind of usage.
- size#
PositiveBigIntegerFieldSize
This is the size of the file in bytes. This can be different than the character length of a text file, since UTF-8 encoding can use anywhere between 1-4 bytes to represent any given character.
- Type:
Type
- text#
MultiCollationTextFieldText
The
textfield contains the text representation of theMedia, if it is available. A blank value means means that we are storing text for thisMedia, and that text happens to be an empty string. A null value here means that we are not storing any text here, and theMediaexists only in file form. It is an error fortextto be None andhas_fileto be False, since that would mean we haven’t stored data anywhere at all.- Type:
Type
- class openedx_content.models_api.MediaType(*args, **kwargs)#
Bases:
ModelStores Media types for use by
Mediamodels.This is the same as MIME types (the IANA renamed MIME Types to Media Types). We don’t pre-populate this table, so APIs that add
Mediamust ensure that the desired Media Type exists.Media types are written as
{type}/{sub_type}+{suffix}, where suffixes are seldom used. Examples:application/jsontext/cssimage/svg+xmlapplication/vnd.openedx.xblock.v1.problem+xml
We have this as a separate model (instead of a field on
Media) because:We can save a lot on storage and indexing for
Mediaif we’re just storing foreign key references there, rather than the entire content string to be indexed. This is especially relevant for our (long) custom types likeapplication/vnd.openedx.xblock.v1.problem+xml.These values can occasionally change. For instance,
text/javascriptvs.application/javascript. Also, we will be using a fair number ofvnd.style of custom content types, and we may want the flexibility of changing that without having to worry about migrating millions of rows ofMedia.
Media types are denoted as
{type}/{sub_type}+{suffix}. We currently do not support parameters.- Parameters:
id (AutoField) –
Primary key: Id
We’re going to have many foreign key references from
Mediainto this model, and we don’t need to store those as 8-byteBigAutoField, as is the default for this app. It’s likely that aSmallAutoFieldwould work, but I can just barely imagine using more than 32K Media types if we have a bunch of customvnd.entries, or start tracking suffixes and parameters. Which is how we end up at the 4-byteAutoField.type (MultiCollationCharField) –
Type
Media type, e.g.
application,text,image. Per RFC 4288, this can be at most 127 chars long and is case insensitive. In practice, it’s almost always written in lowercase.sub_type (MultiCollationCharField) –
Sub type
Media sub-type, e.g.
json,css,png. Per RFC 4288, this can be at most 127 chars long and is case insensitive. In practice, it’s almost always written in lowercase.suffix (MultiCollationCharField) –
Suffix
Suffix, like
xml(e.g.image/svg+xml). Usually blank. I couldn’t find an RFC description of the length limit, and 127 is probably excessive. But this table should be small enough where it doesn’t really matter.
Reverse relationships:
- Parameters:
media (Reverse
ForeignKeyfromMedia) – All Media of this media type (related name ofmedia_type)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- id#
-
Primary key: Id
We’re going to have many foreign key references from
Mediainto this model, and we don’t need to store those as 8-byteBigAutoField, as is the default for this app. It’s likely that aSmallAutoFieldwould work, but I can just barely imagine using more than 32K Media types if we have a bunch of customvnd.entries, or start tracking suffixes and parameters. Which is how we end up at the 4-byteAutoField.- Type:
Type
- sub_type#
MultiCollationCharFieldSub type
Media sub-type, e.g.
json,css,png. Per RFC 4288, this can be at most 127 chars long and is case insensitive. In practice, it’s almost always written in lowercase.- Type:
Type
- suffix#
MultiCollationCharFieldSuffix
Suffix, like
xml(e.g.image/svg+xml). Usually blank. I couldn’t find an RFC description of the length limit, and 127 is probably excessive. But this table should be small enough where it doesn’t really matter.- Type:
Type
- type#
MultiCollationCharFieldType
Media type, e.g.
application,text,image. Per RFC 4288, this can be at most 127 chars long and is case insensitive. In practice, it’s almost always written in lowercase.- Type:
Type
- class openedx_content.models_api.PublishLog(*args, **kwargs)#
Bases:
ModelThere is one row in this table for every time content is published.
Each
PublishLoghas 0 or morePublishLogRecordobjects describing exactly whichPublishableEntityobjects were published and what the version changes are. APublishLogis like a git commit in that sense, with individualPublishLogRecordobjects representing the files changed.Open question: Empty publishes are allowed at this time, and might be useful for “fake” publishes that are necessary to invoke other post-publish actions. It’s not clear at this point how useful this will actually be.
The absence of a
version_numfield in this model is intentional, because having one would potentially cause write contention/locking issues when there are many people working on different entities in a very large library. We already see some contention issues occuring in ModuleStore for courses, and we want to support Libraries that are far larger.If you need a
LearningPackage-wide indicator for version and the only thing you care about is “has something changed?”, you can make a foreign key to the most recentPublishLog, or use the most recentPublishLog’s primary key. This should be monotonically increasing, though there will be large gaps in values, e.g. (5, 190, 1291, etc.). Be warned that this value will not port across sites. If you need site-portability, the UUIDs for this model are a safer bet, though there’s a lot about import/export that we haven’t fully mapped out yet.- Parameters:
id (BigAutoField) – Primary key: ID
uuid (UUIDField) – UUID
message (MultiCollationCharField) – Message
published_at (DateTimeField) – Published at
Relationship fields:
- Parameters:
learning_package (
ForeignKeytoLearningPackage) – Learning package (related name:publishlog)published_by (
ForeignKeytoUser) – Published by (related name:publishlog)
Reverse relationships:
- Parameters:
records (Reverse
ForeignKeyfromPublishLogRecord) – All records of this Publish Log (related name ofpublish_log)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.PublishLogRecord(*args, **kwargs)#
Bases:
ModelA record for each publishable entity version changed, for each publish.
To revert a publish, we would make a new publish that swaps
old_versionandnew_versionfield values.If the
old_versionandnew_versionof aPublishLogRecordmatch, it means that the definition of the entity itself did not change (i.e. no newPublishableEntityVersionwas created), but something else was published that had the side-effect of changing the published state of this entity. For instance, if a Unit has unpinned references to its child Components (which it almost always will), then publishing one of those Components will alter the published state of the Unit, even if the UnitVersion does not change.- Parameters:
id (BigAutoField) – Primary key: ID
dependencies_hash_digest (CharField) –
Dependencies hash digest
The
dependencies_hash_digestis used when the version alone isn’t enough to let us know the full draft state of an entity. This happens any time aPublishedversion has dependencies (see thePublishableEntityVersionDependencymodel), because changes in those dependencies will cause changes to the state of theDraft. The main example of this is containers, where changing an unpinned child affects the state of the parent container, even if that container’s definition (and thus version) does not change.If a
Publishedversion has no dependencies, then its entire state is captured by its version, and thedependencies_hash_digestis blank. (Blank is slightly more convenient for database comparisons than NULL.)Note: There is an equivalent of this field in the
Draftmodel and the the values may drift away from each other.direct (BooleanField) –
Direct
The
directfield captures user intent during the publishing process. It is True if the user explicitly requested to publish the entity represented by thisPublishLogRecord—i.e. they clicked “publish” on this entity or selected it for bulk publish.This field is False if this entity was indirectly published either as a child/dependency or side-effect of a directly published entity.
If this field is None, that means that this
PublishLogRecordwas created before we started capturing user intent (pre-Verawood release), and we cannot reliably infer what the user clicked on. For example, say we had a Subsection > Unit > Component arrangement where the Component had an unpublished change. The user is allowed to press the “publish” button at the Subsection, Unit, or Component levels in the UI. Before we started recording this field, the resultingPublishLogobjects would have looked identical in all three cases: a version changePublishLogRecordfor Component, and side-effect records for the Unit and Subsection. Therefore, we cannot accurately backfill this field.Here are some examples to illustrate how
directgets set and why:Example 1: The user clicks “publish” on a Component that’s in a Unit.
The Component has
direct=True, but the side-effectPublishLogRecordfor the Unit hasdirect=False. Likewise, any side-effect records at higher levels (subsection, section) also havedirect=False.Example 2: The user clicks “publish” on a Unit, where both the Unit and Component have unpublished changes.
In this case, the Unit has
direct=True, and the Component hasdirect=False. The draft status of the Component is irrelevant. The user asked for the Unit to the published, so the Unit’sPublishLogRecordis the only thing that getsdirect=True.Example 3: The user clicks “publish” on a Unit that has no changes of its own (draft version == published version), but the Unit contains a Component that has changes.
Again, only the
PublishLogRecordfor the Unit hasdirect=True. The Component’sPublishLogRecordhasdirect=False. Even though the Unit’s publishedversion_numdoes not change (i.e. it is purely a side-effect publish), the user intent was to publish the Unit (and anything it contains), so the Unit getsdirect=True.Example 4: The user selects multiple entities for bulk publishing.
Those exact entities that the user selected get
direct=True. It does not matter if some of those entities are children of other selected items or not. Other entries like dependencies or side-effects havedirect=False.Example 5: The user selects “publish all”.
Selecting “publish all” in our system currently translates into “publish all the entities that have a draft version that is different from its published version”. Those entities would get
PublishLogRecordobjects withdirect=True, while all side-effects would get records withdirect=False. So if a Unit’s draft and published versions match, and one of its Components has unpublished changes, then “publish all” would cause the Component’s record to havedirect=Trueand the Unit’s record to havedirect=False.
Relationship fields:
- Parameters:
publish_log (
ForeignKeytoPublishLog) – Publish log (related name:records)entity (
ForeignKeytoPublishableEntity) – Entity (related name:publishlogrecord)old_version (
ForeignKeytoPublishableEntityVersion) – Old version (related name:+)new_version (
ForeignKeytoPublishableEntityVersion) – New version (related name:publishlogrecord)
Reverse relationships:
- Parameters:
published (Reverse
ForeignKeyfromPublished) – All Published Entities of this Publish Log Record (related name ofpublish_log_record)causes (Reverse
ForeignKeyfromPublishSideEffect) – All causes of this Publish Log Record (related name ofcause)affected_by (Reverse
ForeignKeyfromPublishSideEffect) – All affected by of this Publish Log Record (related name ofeffect)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- dependencies_hash_digest#
-
Dependencies hash digest
The
dependencies_hash_digestis used when the version alone isn’t enough to let us know the full draft state of an entity. This happens any time aPublishedversion has dependencies (see thePublishableEntityVersionDependencymodel), because changes in those dependencies will cause changes to the state of theDraft. The main example of this is containers, where changing an unpinned child affects the state of the parent container, even if that container’s definition (and thus version) does not change.If a
Publishedversion has no dependencies, then its entire state is captured by its version, and thedependencies_hash_digestis blank. (Blank is slightly more convenient for database comparisons than NULL.)Note: There is an equivalent of this field in the
Draftmodel and the the values may drift away from each other.- Type:
Type
- direct#
-
Direct
The
directfield captures user intent during the publishing process. It is True if the user explicitly requested to publish the entity represented by thisPublishLogRecord—i.e. they clicked “publish” on this entity or selected it for bulk publish.This field is False if this entity was indirectly published either as a child/dependency or side-effect of a directly published entity.
If this field is None, that means that this
PublishLogRecordwas created before we started capturing user intent (pre-Verawood release), and we cannot reliably infer what the user clicked on. For example, say we had a Subsection > Unit > Component arrangement where the Component had an unpublished change. The user is allowed to press the “publish” button at the Subsection, Unit, or Component levels in the UI. Before we started recording this field, the resultingPublishLogobjects would have looked identical in all three cases: a version changePublishLogRecordfor Component, and side-effect records for the Unit and Subsection. Therefore, we cannot accurately backfill this field.Here are some examples to illustrate how
directgets set and why:Example 1: The user clicks “publish” on a Component that’s in a Unit.
The Component has
direct=True, but the side-effectPublishLogRecordfor the Unit hasdirect=False. Likewise, any side-effect records at higher levels (subsection, section) also havedirect=False.Example 2: The user clicks “publish” on a Unit, where both the Unit and Component have unpublished changes.
In this case, the Unit has
direct=True, and the Component hasdirect=False. The draft status of the Component is irrelevant. The user asked for the Unit to the published, so the Unit’sPublishLogRecordis the only thing that getsdirect=True.Example 3: The user clicks “publish” on a Unit that has no changes of its own (draft version == published version), but the Unit contains a Component that has changes.
Again, only the
PublishLogRecordfor the Unit hasdirect=True. The Component’sPublishLogRecordhasdirect=False. Even though the Unit’s publishedversion_numdoes not change (i.e. it is purely a side-effect publish), the user intent was to publish the Unit (and anything it contains), so the Unit getsdirect=True.Example 4: The user selects multiple entities for bulk publishing.
Those exact entities that the user selected get
direct=True. It does not matter if some of those entities are children of other selected items or not. Other entries like dependencies or side-effects havedirect=False.Example 5: The user selects “publish all”.
Selecting “publish all” in our system currently translates into “publish all the entities that have a draft version that is different from its published version”. Those entities would get
PublishLogRecordobjects withdirect=True, while all side-effects would get records withdirect=False. So if a Unit’s draft and published versions match, and one of its Components has unpublished changes, then “publish all” would cause the Component’s record to havedirect=Trueand the Unit’s record to havedirect=False.- Type:
Type
- class openedx_content.models_api.PublishSideEffect(*args, **kwargs)#
Bases:
ModelModel to track when a change in one
Publishedentity affects others.Our first use case for this is that changes involving child components are thought to affect parent Units, even if the parent’s version doesn’t change.
Side-effects are recorded in a collapsed form that only captures one level. So if Components C1 and C2 are both published and they are part of Unit U1, which is in turn a part of Subsection SS1, then the
PublishSideEffectentries are:(C1, U1) (C2, U1) (U1, SS1)
We do not keep entries for
(C1, SS1)or(C2, SS1). This is to make the model simpler, so we don’t have to differentiate between direct side-effects and transitive side-effects in the model.- Parameters:
id (BigAutoField) – Primary key: ID
Relationship fields:
- Parameters:
cause (
ForeignKeytoPublishLogRecord) – Cause (related name:causes)effect (
ForeignKeytoPublishLogRecord) – Effect (related name:affected_by)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.PublishableContentModelRegistry#
Bases:
objectThis class tracks content models built on
PublishableEntity(andPublishableEntityVersion).- classmethod register(content_model_cls: type[PublishableEntityMixin], content_version_model_cls: type[PublishableEntityVersionMixin])#
Register what content model maps to what content version model.
If you want to call this from another app, please use the
register_publishable_modelsfunction in this app’sapimodule instead.
- class openedx_content.models_api.PublishableEntity(*args, **kwargs)#
Bases:
ModelThis represents any publishable thing that has ever existed in a
LearningPackage. It serves as a stable model that will not go away even if these things are later unpublished or deleted.A
PublishableEntitybelongs to exactly oneLearningPackage.Examples of Publishable Entities#
Componentobjects (e.g. VideoBlock, ProblemBlock), Units, and Sections/Subsections would all be considered Publishable Entites. But anything that can be imported, exported, published, and reverted in a course or library could be modeled as aPublishableEntity, including things like Grading Policy or possibly Taxonomies (?).How to use this model#
The publishing app understands that publishable entities exist, along with their drafts and published versions. It has some basic metadata, such as identifiers, who created it, and when it was created. It’s meant to encapsulate the draft and publishing related aspects of your content, but the
publishingapp doesn’t know anything about the actual content being referenced.You have to provide actual meaning to
PublishableEntityby creating your own models that will represent your particular content and associating them toPublishableEntityvia aOneToOneFieldwithprimary_key=True. The easiest way to do this is to have your model inherit fromPublishableEntityMixin.Identifiers#
The UUID is globally unique and should be treated as immutable.
The key field is mutable, but changing it will affect all
PublishableEntityVersionobjects. They are locally unique within theLearningPackage.If you are referencing this model from within the same process, use a foreign key to the id. If you are referencing this
PublishableEntityfrom an external system/service, use the UUID. The key is the part that is most likely to be human-readable, and may be exported/copied, but try not to rely on it, since this value may change.Note: When we actually implement the ability to change identifiers, we should make a history table and a modified attribute on this model.
Why are Identifiers in this Model?#
A
PublishableEntitynever stands alone–it’s always intended to be used with a 1:1 model likeComponentor Unit. So why have all the identifiers in this model instead of storing them in those other models? Two reasons:Published things need to have the right identifiers so they can be used throughout the system, and the UUID is serving the role of ISBN in physical book publishing.
We want to be able to enforce the idea that
entity_refis locally unique across allPublishableEntityobjects within a givenLearningPackage.Componentand Unit can’t do that without a shared model.
That being said, models that build on
PublishableEntityare free to add their own identifiers if it’s useful to do so.Why not Inherit from this Model?#
Django supports multi-table inheritance.
We don’t use that, primarily because we want to more clearly decouple publishing concerns from the rest of the logic around
Componentobjects, Units, etc. If you made aComponentandComponentVersionmodels that subclassedPublishableEntityandPublishableEntityVersion, and then accessedcomponent.versions, you might expectComponentVersionobjects to come back and be surprised when you get EntityVersions instead.In general, we want freedom to add new Publishing models, fields, and methods without having to worry about the downstream name collisions with other apps (many of which might live in other repositories). The helper mixins will provide a little syntactic sugar to make common access patterns more convenient, like file access.
- param id:
Primary key: Id
- type id:
~openedx_content.applets.publishing.models.publishable_entity.IDField
- param uuid:
UUID
- type uuid:
~django.db.models.UUIDField
- param entity_ref:
Entity ref
entity_refis an opaque reference string assigned by the creator of this entity (e.g. derived fromcomponent_type + component_codeforComponentobjects). Consumers must treat it as an atomic string — do not parse or reconstruct it.- type entity_ref:
~openedx_django_lib.fields.MultiCollationCharField
- param created:
Created
All
PublishableEntityVersionobjects created as part of the same publish should have the exact same created datetime (not off by a handful of microseconds).- type created:
~django.db.models.DateTimeField
- param can_stand_alone:
Can stand alone. Set to True when created independently, False when created as part of a container.
- type can_stand_alone:
~django.db.models.BooleanField
Relationship fields:
- param learning_package:
Learning package (related name:
publishable_entities)- type learning_package:
- param created_by:
Created by (related name:
publishableentity)User who created the
PublishableEntityVersion. This can be null if the user is later removed. Open edX in general doesn’t let you remove users, but we should try to model it so that this is possible eventually.- type created_by:
ForeignKeytoUser
Reverse relationships:
- param versions:
All versions of this Publishable Entity (related name of
entity)- type versions:
Reverse
ForeignKeyfromPublishableEntityVersion- param affects:
All affects of this Publishable Entity (related name of
dependencies)- type affects:
Reverse
ManyToManyFieldfromPublishableEntityVersion- param publishableentityversiondependency:
All publishable entity version dependencys of this Publishable Entity (related name of
referenced_entity)- type publishableentityversiondependency:
Reverse
ForeignKeyfromPublishableEntityVersionDependency- param draft:
The draft of this Publishable Entity (related name of
entity)- type draft:
Reverse
OneToOneFieldfromDraft- param draftchangelogrecord:
All Draft Change Log Records of this Publishable Entity (related name of
entity)- type draftchangelogrecord:
Reverse
ForeignKeyfromDraftChangeLogRecord- param publishlogrecord:
All Publish Log Records of this Publishable Entity (related name of
entity)- type publishlogrecord:
Reverse
ForeignKeyfromPublishLogRecord- param published:
The Published Entity of this Publishable Entity (related name of
entity)- type published:
Reverse
OneToOneFieldfromPublished- param collections:
All collections of this Publishable Entity (related name of
entities)- type collections:
Reverse
ManyToManyFieldfromCollection- param collectionpublishableentity:
All collection publishable entitys of this Publishable Entity (related name of
entity)- type collectionpublishableentity:
Reverse
ForeignKeyfromCollectionPublishableEntity- param component:
The Component of this Publishable Entity (related name of
publishable_entity)- type component:
Reverse
OneToOneFieldfromComponent- param entitylistrow:
All entity list rows of this Publishable Entity (related name of
entity)- type entitylistrow:
Reverse
ForeignKeyfromEntityListRow- param container:
The container of this Publishable Entity (related name of
publishable_entity)- type container:
Reverse
OneToOneFieldfromContainer- param testentity:
The test entity of this Publishable Entity (related name of
publishable_entity)- type testentity:
Reverse
OneToOneFieldfromTestEntity
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- class IDField(*args, **kwargs)#
Bases:
TypedBigAutoField
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.PublishableEntityMixin(*args, **kwargs)#
Bases:
ModelConvenience mixin to link your models against
PublishableEntity.Please see docstring for
PublishableEntityfor more details.If you use this class, you MUST also use
PublishableEntityVersionMixinand the publishing app’sapi.register_publishable_models(see its docstring for details).Relationship fields:
- Parameters:
publishable_entity (
OneToOneFieldtoPublishableEntity) – Primary key: Publishable entity
- class VersioningHelper(content_obj)#
Bases:
objectHelper class to link content models to their versions.
The publishing app has
PublishableEntityandPublishableEntityVersion. This is a helper class so that if you mixPublishableEntityMixininto a content model likeComponent, then you can do something like:component.versioning.draft # current draft ComponentVersion component.versioning.published # current published ComponentVersion
It links the relationships between content models and their versioned counterparts through the connection between
PublishableEntityandPublishableEntityVersion. Socomponent.versioning.draftends up querying:Component→PublishableEntity→Draft→PublishableEntityVersion→ComponentVersion. But the people writingComponentdon’t need to understand how the publishing models work to do these common queries.Caching Warning#
Note that because we’re just using the underlying model’s relations, calling this a second time will returned the cached relation, and not cause a fetch of new data from the database. So for instance, if you do:
# Create a new Component + ComponentVersion component, component_version = create_component_and_version( learning_package_id=learning_package.id, namespace="xblock.v1", type="problem", component_code="monty_hall", title="Monty Hall Problem", created=now, created_by=None, ) # This will work, because it's never been published assert component.versioning.published is None # Publishing happens publish_all_drafts(learning_package.id, published_at=now) # This will FAIL because it's going to use the relation value # cached on component instead of going to the database again. # You need to re-fetch the component for this to work. assert component.versioning.published == component_version # You need to manually refetch it from the database to see the new # publish status: component = get_component(component.id) # Now this will work: assert component.versioning.published == component_version
TODO: This probably means we should use a custom Manager to select related fields.
- __init__(content_obj)#
- property draft#
Return the content version object that is the current draft.
So if you mix
PublishableEntityMixinintoComponent, thencomponent.versioning.draftwill return you theComponentVersionthat is the current draft (not the underlyingPublishableEntityVersion).If this is causing many queries, it might be the case that you need to add
select_related('publishable_entity__draft__version')to the queryset.
- property has_unpublished_changes#
Do we have unpublished changes?
The simplest way to implement this would be to check self.published vs. self.draft, but that would cause unnecessary queries. This implementation should require no extra queries provided that the model was instantiated using a queryset that used a select related that has at least
publishable_entity__draftandpublishable_entity__published.
- property last_publish_log#
Return the most recent
PublishLogfor this component.Return None if the component is not published.
- property latest#
Return the most recently created version for this content object.
This can be None if no versions have been created.
This is often the same as the draft version, but can differ if the content object was soft deleted or the draft was reverted.
- property published#
Return the content version object that is currently published.
So if you mix
PublishableEntityMixinintoComponent, thencomponent.versioning.publishedwill return you theComponentVersionthat is currently published (not the underlyingPublishableEntityVersion).If this is causing many queries, it might be the case that you need to add
select_related('publishable_entity__published__version')to the queryset.
- version_num(version_num)#
Return a specific numbered version model.
- property versions#
Return a QuerySet of content version models for this content model.
Example: If you mix
PublishableEntityMixininto aComponentmodel, this would return you a QuerySet ofComponentVersionmodels.
- class openedx_content.models_api.PublishableEntityVersion(*args, **kwargs)#
Bases:
ModelA particular version of a
PublishableEntity.This model has its own
uuidso that it can be referenced directly. Theuuidshould be treated as immutable.PublishableEntityVersionobjects are created once and never updated. So for instance, thetitleshould never be modified.Like
PublishableEntity, the data in this model is only enough to cover the parts that are most important for the actual process of managing drafts and publishes. You will want to create your own models to represent the actual content data that’s associated with thisPublishableEntityVersion, and connect them using aOneToOneFieldwithprimary_key=True. The easiest way to do this is to inherit fromPublishableEntityVersionMixin. Be sure to treat these versioned models in your app as immutable as well.- Parameters:
id (BigAutoField) – Primary key: ID
uuid (UUIDField) – UUID
title (MultiCollationCharField) –
Title
Most publishable things will have some sort of title, but blanks are allowed for those that don’t require one.
version_num (PositiveIntegerField) –
Version num
The
version_numstarts at 1 and increments by 1 with each new version for a givenPublishableEntity. Doing it this way makes it more convenient for users to refer to than a hash or UUID value. It also helps us catch race conditions on save, by setting a unique constraint on the entity andversion_num.created (DateTimeField) –
Created
All
PublishableEntityVersionobjects created as part of the same publish should have the exact same created datetime (not off by a handful of microseconds).
Relationship fields:
- Parameters:
entity (
ForeignKeytoPublishableEntity) – Entity (related name:versions)created_by (
ForeignKeytoUser) –Created by (related name:
publishableentityversion)User who created the
PublishableEntityVersion. This can be null if the user is later removed. Open edX in general doesn’t let you remove users, but we should try to model it so that this is possible eventually.dependencies (
ManyToManyFieldtoPublishableEntity) – Dependencies (related name:affects)
Reverse relationships:
- Parameters:
publishableentityversiondependency (Reverse
ForeignKeyfromPublishableEntityVersionDependency) – All publishable entity version dependencys of this Publishable Entity Version (related name ofreferring_version)draft (Reverse
OneToOneFieldfromDraft) – The draft of this Publishable Entity Version (related name ofversion)draftchangelogrecord (Reverse
ForeignKeyfromDraftChangeLogRecord) – All Draft Change Log Records of this Publishable Entity Version (related name ofnew_version)publishlogrecord (Reverse
ForeignKeyfromPublishLogRecord) – All Publish Log Records of this Publishable Entity Version (related name ofnew_version)published (Reverse
OneToOneFieldfromPublished) – The Published Entity of this Publishable Entity Version (related name ofversion)componentversion (Reverse
OneToOneFieldfromComponentVersion) – The Component Version of this Publishable Entity Version (related name ofpublishable_entity_version)containerversion (Reverse
OneToOneFieldfromContainerVersion) – The container version of this Publishable Entity Version (related name ofpublishable_entity_version)testentityversion (Reverse
OneToOneFieldfromTestEntityVersion) – The test entity version of this Publishable Entity Version (related name ofpublishable_entity_version)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- created#
-
Created
All
PublishableEntityVersionobjects created as part of the same publish should have the exact same created datetime (not off by a handful of microseconds).- Type:
Type
- created_by#
ForeignKeytoUserCreated by (related name:
publishableentityversion)User who created the
PublishableEntityVersion. This can be null if the user is later removed. Open edX in general doesn’t let you remove users, but we should try to model it so that this is possible eventually.- Type:
Type
- title#
MultiCollationCharFieldTitle
Most publishable things will have some sort of title, but blanks are allowed for those that don’t require one.
- Type:
Type
- version_num#
-
Version num
The
version_numstarts at 1 and increments by 1 with each new version for a givenPublishableEntity. Doing it this way makes it more convenient for users to refer to than a hash or UUID value. It also helps us catch race conditions on save, by setting a unique constraint on the entity andversion_num.- Type:
Type
- class openedx_content.models_api.PublishableEntityVersionDependency(*args, **kwargs)#
Bases:
ModelTrack the
PublishableEntityobjects that aPublishableEntityVersiondepends on.For example, a partcular version of a Unit (U1.v1) might be defined to have unpinned references to Components C1 and C2. That means that any changes in C1 or C2 will affect U1.v1 via
DraftSideEffectandPublishSideEffect. We say that C1 and C2 are dependencies of U1.v1.An important restriction is that a
PublishableEntityVersion’s list of dependencies are defined when the version is created. It is not modified after that. No matter what happens to C1 or C2 (e.g. edit, deletion, un-deletion, reset-draft-version-to-published), they will always be dependencies of U1.v1.If someone removes C2 from U1, then that requires creating a new version of U1 (so U1.v2).
This restriction is important because our ability to calculate and cache the state of “this version of this publishable entity and all its dependencies (children)” relies on this being true.
- Parameters:
id (BigAutoField) – Primary key: ID
Relationship fields:
- Parameters:
referring_version (
ForeignKeytoPublishableEntityVersion) – Referring version (related name:publishableentityversiondependency)referenced_entity (
ForeignKeytoPublishableEntity) – Referenced entity (related name:publishableentityversiondependency)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.PublishableEntityVersionMixin(*args, **kwargs)#
Bases:
ModelConvenience mixin to link your models against
PublishableEntityVersion.Please see docstring for
PublishableEntityVersionfor more details.If you use this class, you MUST also use
PublishableEntityMixinand the publishing app’sapi.register_publishable_models(see its docstring for details).Relationship fields:
- Parameters:
publishable_entity_version (
OneToOneFieldtoPublishableEntityVersion) – Primary key: Publishable entity version
- class openedx_content.models_api.Published(*args, **kwargs)#
Bases:
ModelFind the currently published version of an entity.
Notes:
There is only ever one published
PublishableEntityVersionperPublishableEntityat any given time.It may be possible for a
PublishableEntityto exist only as aDraft(and thus not show up in this table).If a row exists for a
PublishableEntity, but theversionfield is None, it means that the entity was published at some point, but is no longer published now–i.e. it’s functionally “deleted”, even though all the version history is preserved behind the scenes.
Relationship fields:
- Parameters:
entity (
OneToOneFieldtoPublishableEntity) – Primary key: Entity (related name:published)version (
OneToOneFieldtoPublishableEntityVersion) – Version (related name:published)publish_log_record (
ForeignKeytoPublishLogRecord) – Publish log record (related name:published)
- exception DoesNotExist#
Bases:
ObjectDoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.Section(*args, **kwargs)#
Bases:
ContainerA Section is type of
Containerthat holdsSubsectionchildren.Via
Containerand itsPublishableEntityMixin, Sections are also publishable entities and can be added to other containers.- Parameters:
container_code (MultiCollationCharField) – Container code
Relationship fields:
- Parameters:
publishable_entity (
OneToOneFieldtoPublishableEntity) – Primary key: Publishable entity (related name:container)learning_package (
ForeignKeytoLearningPackage) – Learning package (related name:container)container_type (
ForeignKeytoContainerType) – Container type (related name:container)container (
OneToOneFieldtoContainer) – Primary key: Container (related name:section)
Reverse relationships:
- Parameters:
versions (Reverse
ForeignKeyfromContainerVersion) – All versions of this container (related name ofcontainer)
- exception DoesNotExist#
Bases:
DoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- olx_tag_name: str = 'chapter'#
The OLX
<tag_name>for XML serialization. Subclasses _may_ override this. Only used in openedx-platform at the moment. We’ll likely have to replace this with something more sophisticated.
- classmethod validate_entity(entity: PublishableEntity) None#
Check if the given entity is allowed as a child of a Section
- class openedx_content.models_api.SectionVersion(*args, **kwargs)#
Bases:
ContainerVersionA SectionVersion is a specific version of a
Section.Via
ContainerVersionand itsEntityList, it defines the list ofSubsectionchildren in this version of theSection.Relationship fields:
- Parameters:
publishable_entity_version (
OneToOneFieldtoPublishableEntityVersion) – Primary key: Publishable entity version (related name:containerversion)container (
ForeignKeytoContainer) – Container (related name:versions)entity_list (
ForeignKeytoEntityList) – Entity list (related name:container_versions)container_version (
OneToOneFieldtoContainerVersion) – Primary key: Container version (related name:sectionversion)
- exception DoesNotExist#
Bases:
DoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- class openedx_content.models_api.Subsection(*args, **kwargs)#
Bases:
ContainerA Subsection is type of
Containerthat holdsUnitchildren.Via
Containerand itsPublishableEntityMixin, Subsections are also publishable entities and can be added to other containers.- Parameters:
container_code (MultiCollationCharField) – Container code
Relationship fields:
- Parameters:
publishable_entity (
OneToOneFieldtoPublishableEntity) – Primary key: Publishable entity (related name:container)learning_package (
ForeignKeytoLearningPackage) – Learning package (related name:container)container_type (
ForeignKeytoContainerType) – Container type (related name:container)container (
OneToOneFieldtoContainer) – Primary key: Container (related name:subsection)
Reverse relationships:
- Parameters:
versions (Reverse
ForeignKeyfromContainerVersion) – All versions of this container (related name ofcontainer)
- exception DoesNotExist#
Bases:
DoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- olx_tag_name: str = 'sequential'#
The OLX
<tag_name>for XML serialization. Subclasses _may_ override this. Only used in openedx-platform at the moment. We’ll likely have to replace this with something more sophisticated.
- classmethod validate_entity(entity: PublishableEntity) None#
Check if the given entity is allowed as a child of a Subsection
- class openedx_content.models_api.SubsectionVersion(*args, **kwargs)#
Bases:
ContainerVersionA SubsectionVersion is a specific version of a
Subsection.Via
ContainerVersionand itsEntityList, it defines the list ofUnitchildren in this version of theSubsection.Relationship fields:
- Parameters:
publishable_entity_version (
OneToOneFieldtoPublishableEntityVersion) – Primary key: Publishable entity version (related name:containerversion)container (
ForeignKeytoContainer) – Container (related name:versions)entity_list (
ForeignKeytoEntityList) – Entity list (related name:container_versions)container_version (
OneToOneFieldtoContainerVersion) – Primary key: Container version (related name:subsectionversion)
- exception DoesNotExist#
Bases:
DoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- property subsection: Subsection#
Convenience accessor to the Subsection this version is associated with
- class openedx_content.models_api.Unit(*args, **kwargs)#
Bases:
ContainerA Unit is type of
Containerthat holdsComponentchildren.Via
Containerand itsPublishableEntityMixin, Units are also publishable entities and can be added to other containers.- Parameters:
container_code (MultiCollationCharField) – Container code
Relationship fields:
- Parameters:
publishable_entity (
OneToOneFieldtoPublishableEntity) – Primary key: Publishable entity (related name:container)learning_package (
ForeignKeytoLearningPackage) – Learning package (related name:container)container_type (
ForeignKeytoContainerType) – Container type (related name:container)container (
OneToOneFieldtoContainer) – Primary key: Container (related name:unit)
Reverse relationships:
- Parameters:
versions (Reverse
ForeignKeyfromContainerVersion) – All versions of this container (related name ofcontainer)
- exception DoesNotExist#
Bases:
DoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned
- olx_tag_name: str = 'vertical'#
The OLX
<tag_name>for XML serialization. Subclasses _may_ override this. Only used in openedx-platform at the moment. We’ll likely have to replace this with something more sophisticated.
- classmethod validate_entity(entity: PublishableEntity) None#
Check if the given entity is allowed as a child of a Unit
- class openedx_content.models_api.UnitVersion(*args, **kwargs)#
Bases:
ContainerVersionA UnitVersion is a specific version of a
Unit.Via
ContainerVersionand itsEntityList, it defines the list ofComponentchildren in this version of theUnit.Relationship fields:
- Parameters:
publishable_entity_version (
OneToOneFieldtoPublishableEntityVersion) – Primary key: Publishable entity version (related name:containerversion)container (
ForeignKeytoContainer) – Container (related name:versions)entity_list (
ForeignKeytoEntityList) – Entity list (related name:container_versions)container_version (
OneToOneFieldtoContainerVersion) – Primary key: Container version (related name:unitversion)
- exception DoesNotExist#
Bases:
DoesNotExist
- exception MultipleObjectsReturned#
Bases:
MultipleObjectsReturned