[BE] Skip foreign-key existence queries in CompetencyCriterion.save()
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 76/100
Research direction
Start with CompetencyCriterion.save() in src/openedx_learning/applets/cbe/models/criteria.py and check the existing _validate_rule_source() exclusion in api.py. Add or update tests for foreign-key reads during bulk_update_competency_criteria() with override values and a named profile, while confirming override payload validation still runs. Done means each changed criterion adds two queries and missing references are still rejected by the database.
Written by the indexing model from the issue text.
Description
User Story
As a course author bulk-editing competency criteria, I want the update to skip redundant database reads for each criterion, in order to keep large batches fast.
Description
CompetencyCriterion.save() calls full_clean() with no exclude. As a result, Django runs a separate SELECT on every save to check that each foreign key's row still exists. We need to exclude the foreign keys from that call and let the database's foreign-key constraints enforce them.
Context
- Follow-up from the review of #874 (for #759): https://github.com/openedx/openedx-core/pull/874#discussion_r4297349913
- Measured on #874: each changed criterion costs 4 queries with override values and 5 with a named profile. Of those, 2 (3 with a profile) are
full_clean()looking up the foreign-key rows.
Acceptance Criteria
Scenario: Saving a criterion doesn't re-read the rows it points at
Given an existing criterion
When its rule is changed and it is saved
Then the save writes the criterion and its history record
And it does not read the criterion's group, tag association, or rule profile
Scenario: Bulk update cost no longer includes the foreign-key reads
Given a batch of criteria in one group whose rule the update changes
When the bulk update applies override values, and again when it names a rule profile
Then each changed criterion adds 2 queries to the batch, in both cases
Scenario: Override payload validation still runs on save
Given a criterion
When it is saved with override values whose payload doesn't fit the rule type
Then the save is refused with a validation error, as before
Scenario: A reference to a missing row is still refused
Given a criterion that points at a group, tag association, or rule profile that doesn't exist
When it is saved
Then the database refuses the write
Technical Details
This is a suggested approach, not the source of truth. The User Story, Description, and Acceptance Criteria define what must be true when the work is done.
Approach
- In
CompetencyCriterion.save()insrc/openedx_learning/applets/cbe/models/criteria.py, passexclude=["group", "object_tag", "rule_profile"]tofull_clean()._validate_rule_source()inapi.pyalready uses the same exclude.clean()still runs, so override payload validation is unchanged. - Excluding a field also skips its not-null check. A dangling or missing foreign key now fails as an
IntegrityErrorfrom the database instead of aValidationErrorfromsave(). The API paths look up the group and tag before they build a criterion, so they shouldn't notice. Confirm that no caller catches theValidationErrorfor this case. - On SQLite and PostgreSQL, Django creates foreign-key constraints as
DEFERRABLE INITIALLY DEFERRED, so the database checks a dangling reference at commit, not atsave(). A test for the last scenario needs to force the check, for example withconnection.check_constraints(). MySQL checks immediately. - Pin the per-criterion cost with a
django_assert_num_queriestest onbulk_update_competency_criteria(): a fixed number of queries, plus 2 per changed criterion, for both override values and a named profile.
Additional context
CompetencyRuleProfile.save()in the same file also callsfull_clean()with noexclude(organization,course,competency_taxonomy). Profiles aren't saved in bulk, so it's out of scope here.- Django's
ForeignKey.validate(), which runs the lookup: https://github.com/django/django/blob/stable/5.2.x/django/db/models/fields/related.py
- Dominant language
- Python
- Stars
- 10
- Forks
- 33
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 22
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openedx/openedx-core
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
openedx/openedx-core#831 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
openedx/openedx-core#827 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
openedx/openedx-core#895 ·
Maintainers usually reply within 1 day
-
openedx/openedx-core#894 ·
Maintainers usually reply within 1 day
-
Tag import: reject a parent_id that names a tag the full replace import deletesPossibly taken @ufedaseyeuconsultant claimed this 3 days ago. Open
openedx/openedx-core#893 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in openedx/openedx-core
Similar issues
-
type-bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
push_gh-pages fails on master branch on forksPossibly taken A pull request linked to this issue is open or already merged. Opengood first issue infrastructure
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
kuznia-rdzeni/coreblocks#1135 ·
-
good first issue server
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
documentation priority: high
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
a-lex-s/mealie-telegram-bot#54 ·
Maintainers usually reply within 1 day