Skip to content

Commit e8fecd1

Browse files
Optimize contains_unpublished_changes to check a unit using only 3 queries
1 parent 4ee8f46 commit e8fecd1

File tree

2 files changed

+53
-3
lines changed
  • openedx_learning/apps/authoring/containers
  • tests/openedx_learning/apps/authoring/units

2 files changed

+53
-3
lines changed

openedx_learning/apps/authoring/containers/api.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,16 @@ def contains_unpublished_changes(
551551
itself has unpublished changes, not if its contents do.
552552
"""
553553
if isinstance(container, ContainerEntityMixin):
554-
container = container.container_entity
554+
# The query below pre-loads the data we need but is otherwise the same thing as:
555+
# container = container.container_entity
556+
container = ContainerEntity.objects.select_related(
557+
"publishable_entity",
558+
"publishable_entity__draft",
559+
"publishable_entity__draft__version",
560+
"publishable_entity__draft__version__containerentityversion__defined_list",
561+
).get(pk=container.container_entity)
562+
else:
563+
pass # TODO: select_related if we're given a raw ContainerEntity rather than a ContainerEntityMixin like Unit?
555564
assert isinstance(container, ContainerEntity)
556565

557566
if container.versioning.has_unpublished_changes:
@@ -563,7 +572,11 @@ def contains_unpublished_changes(
563572
# TODO: This is a naive inefficient implementation but hopefully correct.
564573
# Once we know it's correct and have a good test suite, then we can optimize.
565574
# We will likely change to a tracking-based approach rather than a "scan for changes" based approach.
566-
for row in defined_list.entitylistrow_set.filter(draft_version=None):
575+
for row in defined_list.entitylistrow_set.filter(draft_version=None).select_related(
576+
"entity__containerentity",
577+
"entity__draft__version",
578+
"entity__published__version",
579+
):
567580
try:
568581
child_container = row.entity.containerentity
569582
except PublishableEntity.containerentity.RelatedObjectDoesNotExist:

tests/openedx_learning/apps/authoring/units/test_api.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,44 @@ def test_modify_component_after_publish(self):
205205
assert authoring_api.contains_unpublished_changes(unit) == False # No longer contains unpublished changes
206206

207207

208-
# Test query count of contains_unpublished_changes()
208+
def test_query_count_of_contains_unpublished_changes(self):
209+
"""
210+
Checking for unpublished changes in a unit should require a fixed number
211+
of queries, not get more expensive as the unit gets larger.
212+
"""
213+
unit, unit_version = authoring_api.create_unit_and_version(
214+
learning_package_id=self.learning_package.id,
215+
key=f"unit:key",
216+
title="Unit",
217+
created=self.now,
218+
created_by=None,
219+
)
220+
# Add 100 components (unpinned)
221+
component_count = 100
222+
publishable_entities_pks = []
223+
for i in range(0, component_count):
224+
component, _version = authoring_api.create_component_and_version(
225+
self.learning_package.id,
226+
component_type=self.problem_type,
227+
local_key=f"Query Counting {i}",
228+
title=f"Querying Counting Problem {i}",
229+
created=self.now,
230+
)
231+
publishable_entities_pks.append(component.publishable_entity_id)
232+
authoring_api.create_next_unit_version(
233+
unit=unit,
234+
title=unit_version.title,
235+
publishable_entities_pks=publishable_entities_pks,
236+
draft_version_pks=[None] * component_count,
237+
published_version_pks=[None] * component_count, # FIXME: why do we specify this?
238+
created=self.now,
239+
)
240+
authoring_api.publish_all_drafts(self.learning_package.id)
241+
unit.refresh_from_db()
242+
with self.assertNumQueries(3):
243+
assert authoring_api.contains_unpublished_changes(unit) == False
244+
245+
# Test that pinned components with changes don't show up as "contains unpublished changes"
209246
# Test that only components can be added to units
210247
# Test that components must be in the same learning package
211248
# Test that _version_pks=[] arguments must be related to publishable_entities_pks

0 commit comments

Comments
 (0)