Skip to content

feat: adds api to retrieve library block/container hierarchy [FC-0090] #36813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from

Conversation

ChrisChV
Copy link
Contributor

@ChrisChV ChrisChV commented May 29, 2025

Description

  • Adds Hierarchy API for containers and blocks.
  • Test added for publish section and subsections
  • Set None as default of published_by in ContainerMetadata to match the same behaviour in LibraryXBlockMetadata
  • Rename _remove_container_components to _remove_container_children
  • Rename _patch_container_components to _patch_container_children
  • Which edX user roles will this change impact? "Developer"

Supporting information

Link to other information about the change, such as Jira issues, GitHub issues, or Discourse discussions.
Be sure to check they are publicly readable, or if not, repeat the information here.

Depends on: openedx/openedx-learning#333

Testing instructions

See openedx/frontend-app-authoring#2186

Deadline

None

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label May 29, 2025
@openedx-webhooks
Copy link

openedx-webhooks commented May 29, 2025

Thanks for the pull request, @ChrisChV!

This repository is currently maintained by @openedx/wg-maintenance-edx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@ChrisChV ChrisChV marked this pull request as draft May 29, 2025 22:14
@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions May 29, 2025
@ChrisChV ChrisChV force-pushed the chris/FAL-4180-sections-subsections-publish branch from 984a830 to f2c339f Compare May 29, 2025 22:19
Comment on lines +670 to +677
# Removing the unit with components because the components (children of children) are not published.
# If the unit is kept, the subsection continues to have changes even after it is published.
self._remove_container_children(
self.subsection_with_units["id"],
children_ids=[
self.unit_with_components["id"],
]
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradenmacdonald @ormsbee Currently, when publishing a container, the children of the children aren't published. In the case of a subsection, the components of a unit that is a child of a subsection aren't published when the subsection is published. This is something that needs to be fixed, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I have a fairly major revamp of openedx/openedx-learning#307 that I want to get into reviewable shape next week. My hope is that the new models I'm introducing (openedx/openedx-learning#317) will make this more straightforward.

@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Jun 3, 2025
Comment on lines 731 to 734
# Fill in hierarchy up through parents
while level := hierarchy.parent_level(level):
items = list(_get_containers_with_entities(items).all())
hierarchy.append(level, *items)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No action needed: Via openedx/openedx-learning#316 we will soon be adding a new OutlineRoot container type that's used for courses and which can have child containers of any type, including a mix. I don't think this code will need to support that (since they're only for courses not libraries), but thought I should mention it, especially if we ever move this "get hierarchy" functionality into learning core instead of just keeping it in content_libraries.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have to move this into openedx-learning anyway to optimize the queries, so I'll keep this in mind.

@pomegranited pomegranited changed the title test: Test for publish section/subsection feat: adds api to retrieve library block/container hierarchy Jun 28, 2025
@pomegranited pomegranited changed the title feat: adds api to retrieve library block/container hierarchy feat: adds api to retrieve library block/container hierarchy [FC-0090] Jun 28, 2025
@pomegranited pomegranited added the FC Relates to an Axim Funded Contribution project label Jun 28, 2025
Required a refactor of the approach to avoid using the Metadata classes.
@pomegranited pomegranited force-pushed the chris/FAL-4180-sections-subsections-publish branch from 6fedfbb to 195c73e Compare July 2, 2025 13:44
@bradenmacdonald
Copy link
Contributor

@ormsbee Could you please look at this PR together with openedx/openedx-learning#333 (and openedx/frontend-app-authoring#2186 if you want a UI for testing), and let me know your thoughts about the performance and the high query count? As we discussed, it's working fine - just need a read on whether we're going to have perf issues bite us in the future if we merge it like this.

@rpenido rpenido force-pushed the chris/FAL-4180-sections-subsections-publish branch from 131b00a to b2a0cd1 Compare July 23, 2025 17:35
@ormsbee
Copy link
Contributor

ormsbee commented Jul 30, 2025

@bradenmacdonald and @kdmccormick: this touches on what we were talking about yesterday with hierarchy modeling:

High level thoughts:

  1. I do think efficient container traversal has to be added to Learning Core.
  2. I think the APIs should be more QuerySet/model based, i.e. there's something like a ContainerPath model that can be returned, so that other APIs can extend that queryset to add more select_related calls to it.
  3. We would want to avoid encoding specific assumptions about hierarchy, e.g." second level is always a Unit", since we could have different types of containers down the line.
  4. We can probably get away with only tracking the expanded form for the current Draft and Published versions.

So a containers app (or publishing for now) could have a model like:

class PublishedContainerPath(models.Model):
    # level_0 is the lowest/leaf layer, e.g. the Component.
    # This is a DAG, so we can have many duplicates of level_0_entity, and the
    # only UNIQUE restriction would be on the entire row, i.e. exact duplicates
    # of everything would be a race condition. Even having the same entity at
    # multiple versions might be allowable for pinned child references in the
    # future.
    #
    # It feels unintuitive to model the hierarchy from bottom-up like this, but
    # as Braden pointed out, this would let us avoid the issue of having Units
    # show up in multiple columns, since a Unit would always be at level_1 in
    # this type of model. Some containers could show up at multiple levels
    # though, like OutlineRoot  in the form that we've most recently discussed.
    level_0_entity = models.ForeignKey(models.PublishableEntity)
    level_0_version = models.ForeignKey(models.PublishableEntityVersion, null=True)
    level_0_order = models.PositiveSmallInteger(null=True)  # or PositiveInteger?

    # Continue this pattern for at least Component -> Unit -> Subsection ->
    # Section -> OutlineRoot (0-4), but probably at least to 6 to give a little
    # headroom?
    level_1_entity = models.ForeignKey(models.PublishableEntity)
    level_1_version = models.ForeignKey(models.PublishableEntityVersion, null=True)
    level_1_order = models.PositiveSmallInteger(null=True)

    # highest level doesn't have an ordering (because it's never a peer child
    # of anything)
    level_6_entity = models.ForeignKey(models.PublishableEntity)
    level_6_version = models.ForeignKey(models.PublishableEntityVersion, null=True)

    # This is going to be very index heavy, but hopefully the table itself
    # doesn't grow too large if we constrain it to only show published and draft.
    # We can do incremental updates as drafts are updated, i.e. we should never
    # be rewriting a whole course's hierarchy when a single subsection changes.

    # ForeignKeys mean implicit indexes already, so we can do searches for
    # individual containers easily enough. We'd need to add indexes for
    # efficient ordered traversal, which in this model would be a composite
    # index with ordering, e.g. (level_6_entity, level_5_order, level_5_entity,
    # etc.)

This model isn't fully baked, but I think the basic idea is workable. Something flat that represents hierarchy, is easily filterable, and where callers can efficiently tack joins onto the queryset to add whatever data they think is relevant. I think that we can potentially do some really good caching once I can land the publishing/draft dependency stuff, but that the caching would mostly happen at the edges (e.g. content_libraries), while the inner pieces like containers would continue to pass QuerySets to make things more extensible.

@bradenmacdonald
Copy link
Contributor

@ormsbee @kdmccormick I like where that's going!

We would want to avoid encoding specific assumptions about hierarchy, e.g." second level is always a Unit", since we could have different types of containers down the line.

a Unit would always be at level_1 in this type of model.

Are these two statements ^ contradictory, or are you saying "the second-smallest level is always a unit (and so on)" but we don't make any guarantees about containers starting from the largest/root of the hierarchy ?

We can probably get away with only tracking the expanded form for the current Draft and Published versions.

Do you think we'd have separate ContainerPath tables for draft and published?

We would want to avoid encoding specific assumptions about hierarchy, e.g." second level is always a Unit", since we could have different types of containers down the line.

I'm wondering though if it could provide better optimization and validation to make the level_0_entity a foreign key to Component rather than PublishableEntity, and likewise the level_1+_entity a foreign key to Container (and similar for version). Or do the aesthetics and simplicity of always using PublishableEntity here win out?

@ormsbee
Copy link
Contributor

ormsbee commented Aug 2, 2025

We would want to avoid encoding specific assumptions about hierarchy, e.g." second level is always a Unit", since we could have different types of containers down the line.

a Unit would always be at level_1 in this type of model.

Are these two statements ^ contradictory, or are you saying "the second-smallest level is always a unit (and so on)" but we don't make any guarantees about containers starting from the largest/root of the hierarchy ?

My rough thoughts right now are:

  1. Units are always level_1 in this model.
  2. Things at level_1 won't always necessarily be Units, as we add other types. For instance, individual file uploads for things like PDF slides, syllabus, etc. may be stored as something that is not a Component. We might have a container of those that is stored as something that is not a Unit.
  3. Some containers might exist at multiple levels, like OutlineRoot.

I haven't thought about this enough to have firm opinions on any of these.

We can probably get away with only tracking the expanded form for the current Draft and Published versions.

Do you think we'd have separate ContainerPath tables for draft and published?

... I don't know. There's a lot of commonality true, but a unified table is going to have a boolean field in every index. I still feel like they're different models from the point of view of their relationships with other things, but maybe that just really means they're two proxy models on top of the same underlying one. I guess I'd start with seeing how a single table + proxy models would work out.

This is likely to be one of the most confusing models we have for people to grok, even with that indirection.

We would want to avoid encoding specific assumptions about hierarchy, e.g." second level is always a Unit", since we could have different types of containers down the line.

I'm wondering though if it could provide better optimization and validation to make the level_0_entity a foreign key to Component rather than PublishableEntity, and likewise the level_1+_entity a foreign key to Container (and similar for version). Or do the aesthetics and simplicity of always using PublishableEntity here win out?

I'm currently going under the assumption that we will have other things at level 0 eventually. Also, using PublishableEntity references means that container/outline understanding can be lower in the stack and be agnostic to new types of containers that might come about later (as long as they're still essentially ordered lists).

@ormsbee
Copy link
Contributor

ormsbee commented Aug 2, 2025

Random, unbaked thought: In our LC-courses prototype, we mapped course usage keys to publishable entities. I think this is quite reasonable for state management. But one of the other things we use the usage keys for is jump_to links, and right now there's no single identifier for "go to this component in this particular part of the course". Instead, we do some graph traversal to find the left-most appearance and always take you there. Likewise, inheritance has to figure out the authoritative path to this component in order to map out what its attributes are going to be when it's being rendered by itself, with the weird side-effect that a component can seemingly break inheritance rules because you're not looking at it in its first instance in the course.

But we could map usage keys to rows in PublishedContainerPath instead, or have some decorated usage key convention so that we could keep the same user state, but have two different URLs and maybe even two different sets of inherited fields for the same content.

I don't know if this is necessarily a good idea. But it's a dimension of flexibility that we haven't really had before, and it maps pretty squarely onto something like this model.

@ormsbee
Copy link
Contributor

ormsbee commented Aug 4, 2025

Idle thought: I wonder if we can omit the publishable entities and only have publishable entity version references in this ContainerPath model...

@bradenmacdonald
Copy link
Contributor

@ormsbee Since this is blocking openedx/frontend-app-authoring#2186 , do you think we can proceed with merging it as a provisional/unstable API, and perhaps even keeping the test cases as test code, for when we implement a proper hierarchy retrieval system / PublishedContainerPath in the near future?

@bradenmacdonald
Copy link
Contributor

@ChrisChV CC @rpenido I see this PR is still marked as a draft - what's left to do here? Are we just waiting for openedx/openedx-learning#333 ?

@rpenido
Copy link
Contributor

rpenido commented Aug 7, 2025

@ChrisChV CC @rpenido I see this PR is still marked as a draft - what's left to do here? Are we just waiting for openedx/openedx-learning#333 ?

Yes! We can bump the openedx-learning version later if you prefer.

@rpenido rpenido force-pushed the chris/FAL-4180-sections-subsections-publish branch 4 times, most recently from f6f32c0 to 9d5805d Compare August 14, 2025 19:41
@rpenido rpenido force-pushed the chris/FAL-4180-sections-subsections-publish branch from 9d5805d to addb0b5 Compare August 14, 2025 19:48
@rpenido
Copy link
Contributor

rpenido commented Aug 14, 2025

Hi @bradenmacdonald!
I updated here with the new openedx-learning version.
The query count went up a bit from the previous implementation (117 to 133 ef4fd07)

We have some duplicated queries, but I couldn't determine the cause after some time debugging. 😞

E   14. SELECT "oel_publishing_container"."publishable_entity_id", "oel_publishing_publishableentity"."id", "oel_publishing_publishableentity"."uuid", "oel_publishing_publishableentity"."learning_package_id", "oel_publishing_publishableentity"."_key", "oel_publishing_publishableentity"."created", "oel_publishing_publishableentity"."created_by_id", "oel_publishing_publishableentity"."can_stand_alone", "oel_publishing_draft"."entity_id", "oel_publishing_draft"."version_id", "oel_publishing_publishableentityversion"."id", "oel_publishing_publishableentityversion"."uuid", "oel_publishing_publishableentityversion"."entity_id", "oel_publishing_publishableentityversion"."title", "oel_publishing_publishableentityversion"."version_num", "oel_publishing_publishableentityversion"."created", "oel_publishing_publishableentityversion"."created_by_id", "oel_publishing_containerversion"."publishable_entity_version_id", "oel_publishing_containerversion"."container_id", "oel_publishing_containerversion"."entity_list_id", "oel_publishing_entitylist"."id", "oel_publishing_published"."entity_id", "oel_publishing_published"."version_id", "oel_publishing_published"."publish_log_record_id" FROM "oel_publishing_container" INNER JOIN "oel_publishing_publishableentity" ON ("oel_publishing_container"."publishable_entity_id" = "oel_publishing_publishableentity"."id") LEFT OUTER JOIN "oel_publishing_draft" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_draft"."entity_id") LEFT OUTER JOIN "oel_publishing_publishableentityversion" ON ("oel_publishing_draft"."version_id" = "oel_publishing_publishableentityversion"."id") LEFT OUTER JOIN "oel_publishing_containerversion" ON ("oel_publishing_publishableentityversion"."id" = "oel_publishing_containerversion"."publishable_entity_version_id") LEFT OUTER JOIN "oel_publishing_entitylist" ON ("oel_publishing_containerversion"."entity_list_id" = "oel_publishing_entitylist"."id") LEFT OUTER JOIN "oel_publishing_published" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_published"."entity_id") WHERE "oel_publishing_container"."publishable_entity_id" = 578 LIMIT 21
E   15. SELECT "oel_publishing_container"."publishable_entity_id", "oel_publishing_publishableentity"."id", "oel_publishing_publishableentity"."uuid", "oel_publishing_publishableentity"."learning_package_id", "oel_publishing_publishableentity"."_key", "oel_publishing_publishableentity"."created", "oel_publishing_publishableentity"."created_by_id", "oel_publishing_publishableentity"."can_stand_alone", "oel_publishing_draft"."entity_id", "oel_publishing_draft"."version_id", "oel_publishing_publishableentityversion"."id", "oel_publishing_publishableentityversion"."uuid", "oel_publishing_publishableentityversion"."entity_id", "oel_publishing_publishableentityversion"."title", "oel_publishing_publishableentityversion"."version_num", "oel_publishing_publishableentityversion"."created", "oel_publishing_publishableentityversion"."created_by_id", "oel_publishing_containerversion"."publishable_entity_version_id", "oel_publishing_containerversion"."container_id", "oel_publishing_containerversion"."entity_list_id", "oel_publishing_entitylist"."id", "oel_publishing_published"."entity_id", "oel_publishing_published"."version_id", "oel_publishing_published"."publish_log_record_id" FROM "oel_publishing_container" INNER JOIN "oel_publishing_publishableentity" ON ("oel_publishing_container"."publishable_entity_id" = "oel_publishing_publishableentity"."id") LEFT OUTER JOIN "oel_publishing_draft" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_draft"."entity_id") LEFT OUTER JOIN "oel_publishing_publishableentityversion" ON ("oel_publishing_draft"."version_id" = "oel_publishing_publishableentityversion"."id") LEFT OUTER JOIN "oel_publishing_containerversion" ON ("oel_publishing_publishableentityversion"."id" = "oel_publishing_containerversion"."publishable_entity_version_id") LEFT OUTER JOIN "oel_publishing_entitylist" ON ("oel_publishing_containerversion"."entity_list_id" = "oel_publishing_entitylist"."id") LEFT OUTER JOIN "oel_publishing_published" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_published"."entity_id") WHERE "oel_publishing_container"."publishable_entity_id" = 578 LIMIT 21
...
E   31. SELECT "oel_publishing_container"."publishable_entity_id", "oel_publishing_publishableentity"."id", "oel_publishing_publishableentity"."uuid", "oel_publishing_publishableentity"."learning_package_id", "oel_publishing_publishableentity"."_key", "oel_publishing_publishableentity"."created", "oel_publishing_publishableentity"."created_by_id", "oel_publishing_publishableentity"."can_stand_alone", "oel_publishing_draft"."entity_id", "oel_publishing_draft"."version_id", "oel_publishing_publishableentityversion"."id", "oel_publishing_publishableentityversion"."uuid", "oel_publishing_publishableentityversion"."entity_id", "oel_publishing_publishableentityversion"."title", "oel_publishing_publishableentityversion"."version_num", "oel_publishing_publishableentityversion"."created", "oel_publishing_publishableentityversion"."created_by_id", "oel_publishing_containerversion"."publishable_entity_version_id", "oel_publishing_containerversion"."container_id", "oel_publishing_containerversion"."entity_list_id", "oel_publishing_entitylist"."id", "oel_publishing_published"."entity_id", "oel_publishing_published"."version_id", "oel_publishing_published"."publish_log_record_id" FROM "oel_publishing_container" INNER JOIN "oel_publishing_publishableentity" ON ("oel_publishing_container"."publishable_entity_id" = "oel_publishing_publishableentity"."id") LEFT OUTER JOIN "oel_publishing_draft" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_draft"."entity_id") LEFT OUTER JOIN "oel_publishing_publishableentityversion" ON ("oel_publishing_draft"."version_id" = "oel_publishing_publishableentityversion"."id") LEFT OUTER JOIN "oel_publishing_containerversion" ON ("oel_publishing_publishableentityversion"."id" = "oel_publishing_containerversion"."publishable_entity_version_id") LEFT OUTER JOIN "oel_publishing_entitylist" ON ("oel_publishing_containerversion"."entity_list_id" = "oel_publishing_entitylist"."id") LEFT OUTER JOIN "oel_publishing_published" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_published"."entity_id") WHERE "oel_publishing_container"."publishable_entity_id" = 573 LIMIT 21
E   53. SELECT "oel_publishing_container"."publishable_entity_id", "oel_publishing_publishableentity"."id", "oel_publishing_publishableentity"."uuid", "oel_publishing_publishableentity"."learning_package_id", "oel_publishing_publishableentity"."_key", "oel_publishing_publishableentity"."created", "oel_publishing_publishableentity"."created_by_id", "oel_publishing_publishableentity"."can_stand_alone", "oel_publishing_draft"."entity_id", "oel_publishing_draft"."version_id", "oel_publishing_publishableentityversion"."id", "oel_publishing_publishableentityversion"."uuid", "oel_publishing_publishableentityversion"."entity_id", "oel_publishing_publishableentityversion"."title", "oel_publishing_publishableentityversion"."version_num", "oel_publishing_publishableentityversion"."created", "oel_publishing_publishableentityversion"."created_by_id", "oel_publishing_containerversion"."publishable_entity_version_id", "oel_publishing_containerversion"."container_id", "oel_publishing_containerversion"."entity_list_id", "oel_publishing_entitylist"."id", "oel_publishing_published"."entity_id", "oel_publishing_published"."version_id", "oel_publishing_published"."publish_log_record_id" FROM "oel_publishing_container" INNER JOIN "oel_publishing_publishableentity" ON ("oel_publishing_container"."publishable_entity_id" = "oel_publishing_publishableentity"."id") LEFT OUTER JOIN "oel_publishing_draft" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_draft"."entity_id") LEFT OUTER JOIN "oel_publishing_publishableentityversion" ON ("oel_publishing_draft"."version_id" = "oel_publishing_publishableentityversion"."id") LEFT OUTER JOIN "oel_publishing_containerversion" ON ("oel_publishing_publishableentityversion"."id" = "oel_publishing_containerversion"."publishable_entity_version_id") LEFT OUTER JOIN "oel_publishing_entitylist" ON ("oel_publishing_containerversion"."entity_list_id" = "oel_publishing_entitylist"."id") LEFT OUTER JOIN "oel_publishing_published" ON ("oel_publishing_publishableentity"."id" = "oel_publishing_published"."entity_id") WHERE "oel_publishing_container"."publishable_entity_id" = 573 LIMIT 21

Should we merge it, or should I spend more time trying to find the culprit?

@rpenido
Copy link
Contributor

rpenido commented Aug 14, 2025

@ChrisChV Can you move this from draft?

@bradenmacdonald
Copy link
Contributor

@rpenido I think it's fine for now. Let's merge it and we can keep an eye on the performance. Earlier testing on the sandbox seemed to show it was still quite fast. And we do have an eventual plan to do much more aggressive optimization of the performance by caching the whole course tree in learning core.

@ChrisChV ChrisChV marked this pull request as ready for review August 15, 2025 17:29
Copy link
Contributor Author

@ChrisChV ChrisChV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good @rpenido Great! Thanks for this work! 👍

  • I tested this: I followed the testing instructions in openedx/frontend-app-authoring#2186
  • I read through the code and considered the security, stability and performance implications of the changes.
  • I tested that the UI can be used with a keyboard only (tab order, keyboard controls).
  • Includes tests for bugfixes and/or features added.
  • Includes documentation

@ChrisChV
Copy link
Contributor Author

@bradenmacdonald could you review and merge this as CC? Since I created the PR, it needs another CC.

@bradenmacdonald
Copy link
Contributor

@ChrisChV Sure! Could you or @rpenido just add something to the openedx/core/djangoapps/content_libraries/api/containers.py file to mark ALL the API methods in there as unstable (like all the containers APIs in openedx-learning are already marked)? Seems like we forgot to do that. And in particular maybe add a note to the new get_library_object_hierarchy method that we intend to replace it with a more efficient method in the future, and link to #36813 (comment) . I'm going to review now and see if I have any other feedback, but that was one thing I noticed.

Copy link
Contributor

@bradenmacdonald bradenmacdonald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditional approval, if you mark the APIs as unstable and rename this one parameter. Otherwise looks good - thanks!

@@ -145,3 +379,22 @@ def library_container_locator(
container_type=container_type.value,
container_id=container.publishable_entity.key,
)


def get_container_from_key(container_key: LibraryContainerLocator, isDeleted=False) -> Container:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_container_from_key(container_key: LibraryContainerLocator, isDeleted=False) -> Container:
def get_container_from_key(container_key: LibraryContainerLocator, is_deleted=False) -> Container:

Not sure why the linter didn't catch this, but we should use snake_case for parameters

Copy link
Contributor

@rpenido rpenido Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 3e969af

"""
Internal method to fetch the Container object from its LibraryContainerLocator

Raises ContentLibraryContainerNotFound if no container found, or if the container has been soft deleted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to account for the is_deleted parameter. Also is_deleted is a confusing name. You can pass is_deleted=True but then get a container that is NOT deleted. I think it should be include_deleted and the docstring should say "Raises ContentLibraryContainerNotFound if no container found, or if the container has been soft deleted (unless include_deleted is True)"

Copy link
Contributor

@rpenido rpenido Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the param name and put a comment to clarify here: 3e969af.
Could you let me know what you think?

@rpenido
Copy link
Contributor

rpenido commented Aug 16, 2025

@bradenmacdonald I updated the docstring here: 1934a7d

@rpenido rpenido force-pushed the chris/FAL-4180-sections-subsections-publish branch from 949592e to 1934a7d Compare August 16, 2025 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FC Relates to an Axim Funded Contribution project open-source-contribution PR author is not from Axim or 2U
Projects
Status: Waiting on Author
Development

Successfully merging this pull request may close these issues.

6 participants