From b6101a9722d841afa2bc4b43b0ae18b80250f0cb Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 24 Jun 2026 17:31:50 +0530 Subject: [PATCH] Enforce MAX_BLOCKS_PER_CONTENT_LIBRARY when creating containers Container creation bypassed the draft-count limit that already applies to component creation, allowing empty units/sections past the library cap. Co-authored-by: Cursor --- .../djangoapps/content_libraries/api/containers.py | 6 ++++++ .../tests/test_content_libraries.py | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 8952576cb163..cd6aef910534 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -12,9 +12,11 @@ from functools import cache from uuid import UUID, uuid4 +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db.models import F from django.utils.text import slugify +from django.utils.translation import gettext as _ from opaque_keys.edx.locator import LibraryContainerLocator, LibraryLocatorV2, LibraryUsageLocatorV2 from openedx_content import api as content_api from openedx_content.models_api import Container, PublishLogRecord @@ -41,6 +43,7 @@ get_entity_from_key, library_container_locator, ) +from .exceptions import BlockLimitReachedError from .serializers import ContainerSerializer if typing.TYPE_CHECKING: @@ -128,6 +131,9 @@ def create_container( assert isinstance(library_key, LibraryLocatorV2) content_library = ContentLibrary.objects.get_by_key(library_key) assert content_library.learning_package_id # Should never happen but we made this a nullable field so need to check + component_count = content_api.get_all_drafts(content_library.learning_package_id).count() + if component_count + 1 > settings.MAX_BLOCKS_PER_CONTENT_LIBRARY: + raise BlockLimitReachedError(_("Library cannot have more than {} Components.").format(settings.MAX_BLOCKS_PER_CONTENT_LIBRARY)) if slug is None: # Automatically generate a slug. Append a random suffix so it should be unique. slug = slugify(title, allow_unicode=True) + "-" + uuid4().hex[-6:] diff --git a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index d36f45ad1b92..a0f72cc81eb1 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py @@ -778,6 +778,20 @@ def test_library_blocks_limit(self): # Second block should throw error self._add_block_to_library(lib_id, "problem", "problem1", expect_response=400) + def test_library_container_blocks_limit(self): + """ + Test that container creation respects MAX_BLOCKS_PER_CONTENT_LIBRARY. + """ + with self.settings(MAX_BLOCKS_PER_CONTENT_LIBRARY=1): + lib = self._create_library( + slug="test_lib_container_limits", + title="Container Limits Test Library", + description="Testing container limits in a library", + ) + lib_id = lib["id"] + self._add_block_to_library(lib_id, "html", "html1") + self._create_container(lib_id, "unit", None, "Unit 1", expect_response=400) + def test_library_paste_xblock(self): """ Check the a new block is created in the library after pasting from clipboard.