refactor: remove ContentObjectTag model and related functions#34146
Conversation
|
Thanks for the pull request, @rpenido! Please note that it may take us up to several weeks or months to complete a review and merge your PR. Feel free to add as much of the following information to the ticket as you can:
All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here. Please let us know once your PR is ready for our review and all tests are green. |
17db766 to
944a650
Compare
944a650 to
d72ae83
Compare
|
Hi, @pomegranited! I removed a lot of code. Let me know what you think. Also, we will need to override the view here to use this new Implementing this using the rule override would be easier, but I think it's not the best approach: it is more a business rule than a permission one (and also, superusers skip permission checks). |
Rather than overriding the view, we can change the rules to ensure that this gets checked.
We can override that rule here to run the base |
pomegranited
left a comment
There was a problem hiding this comment.
@rpenido Yep, this is looking great. :) I've left a couple inline comments, but haven't done a full review yet, so let me know when it's ready?
| object_key = UsageKey.from_string(object_id) | ||
| except InvalidKeyError: | ||
| try: | ||
| object_key = CourseKey.from_string(object_id) |
There was a problem hiding this comment.
Should use LearningContextKey (not CourseKey) and UsageKey like the previous code -- this supports content library keys too.
There was a problem hiding this comment.
The LearningContextKey doesn't have the org property. It is defined in the CourseKey class:
https://github.com/openedx/opaque-keys/blob/dd532d862a51457a8f0c0f5c936fd57ee4d39fbc/opaque_keys/edx/keys.py#L43-L49
Also, the LibraryLocator inherits from CourseKey:
https://github.com/openedx/opaque-keys/blob/dd532d862a51457a8f0c0f5c936fd57ee4d39fbc/opaque_keys/edx/locator.py#L412
We are using LearningContextKey in many places, but I think we should only use CourseKey | UsageKey (because we rely on the org info to tag content). Do you think this makes sense?
PS: I didn't change it outside of the functions of this refactor.
There was a problem hiding this comment.
I think we should only use CourseKey | UsageKey (because we rely on the org info to tag content)
Ah ok, agreed. I didn't realise LibraryLocator inherited from CourseKey, that's cool.
There was a problem hiding this comment.
Do you think we should create a task to fix this through the code?
There was a problem hiding this comment.
@rpenido I think this is too minor to warrant a task.. but if you're working on affected code, feel free to change it as you go, and cite this discussion.
455105d to
252d402
Compare
252d402 to
739fdb5
Compare
I chose that route. The only issue is that the superuser will skip that rule, but besides that, the code is much cleaner. We were not checking this before, so the query count for tagging count went up a bit. We check TaxonomyOrgs one more time and I think we didn't have a select_related when we get a Taxonomy model. Let me know what you think @pomegranited! |
|
|
||
| def test_cannot_tag_across_orgs(self): | ||
| """ | ||
| Ensure that I cannot apply tags from a taxonomy that's linked to another | ||
| org. | ||
| """ | ||
| # This taxonomy is only linked to the "OpenedX org", so it can't be used for "Axim" content. | ||
| taxonomy = self.taxonomy_one_org | ||
| tags = [self.tag_one_org.value] | ||
| with self.assertRaises(ValueError) as exc: | ||
| api.tag_content_object( | ||
| object_key=CourseKey.from_string("course-v1:Ax+DemoX+Demo_Course"), | ||
| taxonomy=taxonomy, | ||
| tags=tags, | ||
| ) | ||
| assert "The specified Taxonomy is not enabled for the content object's org (Ax)" in str(exc.exception) | ||
| # But this will work fine: | ||
| api.tag_content_object( | ||
| object_key=CourseKey.from_string("course-v1:OeX+DemoX+Demo_Course"), | ||
| taxonomy=taxonomy, | ||
| tags=tags, | ||
| ) | ||
| # As will this: | ||
| api.tag_content_object( | ||
| object_key=CourseKey.from_string("course-v1:Ax+DemoX+Demo_Course"), | ||
| taxonomy=self.taxonomy_both_orgs, | ||
| tags=[self.tag_both_orgs.value], | ||
| ) |
There was a problem hiding this comment.
This test went to the view. We don't check cross-tagging at the API level.
| object_tag = getattr(self, tag_attr) | ||
| assert self.superuser.has_perm(perm, object_tag) | ||
| assert self.staff.has_perm(perm, object_tag) | ||
| assert not self.staff.has_perm(perm, object_tag) |
There was a problem hiding this comment.
This is a side effect of the change. When we didn't check cross-tagging, a staff user could change objecttags from taxonomies without orgs.
There was a problem hiding this comment.
I don't understand why this test changed?
Staff users are "Taxonomy Administrators", just like superusers are, so they should be able to tag objects using no-org taxonomies too.
There was a problem hiding this comment.
The issue is that it is not trivial to differentiate a no-org taxonomy and a taxonomy from another org.
I fixed here: 1a4c858
But now taxonomy admins can also use a Taxonomy from orgA to tag an object from orgB (like superuser).
|
This is ready for a final review @pomegranited! |
pomegranited
left a comment
There was a problem hiding this comment.
I've requested a small change, and I have concerns about the "staff user" permissions change noted in the tests.
But other than that, this is working beautifully, so we're close! I tested this in my devstack, and was able to:
- View/add/remove object tags on units (using openedx/frontend-app-authoring#815)
- View/add/remove object tags on library blocks (using openedx-unsupported/frontend-app-library-authoring#400)
| object_tag = getattr(self, tag_attr) | ||
| assert self.superuser.has_perm(perm, object_tag) | ||
| assert self.staff.has_perm(perm, object_tag) | ||
| assert not self.staff.has_perm(perm, object_tag) |
There was a problem hiding this comment.
I don't understand why this test changed?
Staff users are "Taxonomy Administrators", just like superusers are, so they should be able to tag objects using no-org taxonomies too.
2c7e806 to
2c6986e
Compare
2c6986e to
1a4c858
Compare
pomegranited
left a comment
There was a problem hiding this comment.
One nit, and a comment about the permissions change, but this looks good to merge once the nit is addressed 👍
- I tested this on my devstack:
- Installed this branch on lms/cms and ran migrations
- Set up sample data using https://github.com/open-craft/taxonomy-sample-data/
- Ran openedx/frontend-app-authoring#815 to test tagging units: view, add, remove tags
- Ran openedx-unsupported/frontend-app-library-authoring#400 to test tagging libraries: view, add, remove tags
- I read through the code
-
I checked for accessibility issuesN/A -
Includes documentationN/A -
User-facing strings are extracted for translationN/A
| # Taxonomy admins can tag any object using any taxonomy | ||
| if oel_tagging.is_taxonomy_admin(user): | ||
| return True |
There was a problem hiding this comment.
We can avoid the "Taxonomy admins can tag any object using any taxonomy" issue like this, however: this change bumps the query count for updating object tags up from 7 to 13!
| # Taxonomy admins can tag any object using any taxonomy | |
| if oel_tagging.is_taxonomy_admin(user): | |
| return True | |
| # Only Taxonomy admins can tag objects using a no-org taxonomy | |
| if not perm_obj.taxonomy.taxonomyorg_set.exists(): | |
| return oel_tagging.is_taxonomy_admin(user) |
So I can't actually recommend this change. But, I have a task next sprint to fix these query counts, so I'll handle making this change if needed.
There was a problem hiding this comment.
Besides the query count, I wonder if it is worth making this differentiation: if adminA adds this taxonomy to an org, does this change the permission from adminB?
I'm uncomfortable with this, but I think treating no-org and cross-org the same way (allowing or blocking) would be the best path here.
I commented on the issue you created about the query count.
The staff account is probably the best-case scenario. But I'm sure that you will find what we got wrong.
|
Hi @bradenmacdonald! Could you do a CC review here? |
62acf86 to
dfb908e
Compare
e2439ac to
cc6e2fd
Compare
bradenmacdonald
left a comment
There was a problem hiding this comment.
Do we have tests that you can't tag things that aren't xblocks/courses/libraries?
If so, I think I'm good with this change. Though I did like the API where you could pass UsageKeys directly instead of having to convert them to strings all the time.
|
|
||
| operations = [ | ||
| migrations.DeleteModel( | ||
| name='ContentObjectTag', |
There was a problem hiding this comment.
There is no data stored with this model, right? This won't delete any actual database rows?
There was a problem hiding this comment.
Right. Also, we weren't using it outside auto-tagging: our rest API uses the ObjectTag model (this is the main issue that motivated this PR)
| assert response.status_code == status.HTTP_400_BAD_REQUEST | ||
|
|
||
| @ddt.data( | ||
| # userA and userS are staff in libraryA and can tag using enabled taxonomies |
There was a problem hiding this comment.
Who are userA and userS ? I see them mentioned a few times in this file, but I think those are typos or from an older version. Here I only see user, staff, and staffA.
openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py
Outdated
Show resolved
Hide resolved
| from opaque_keys.edx.locator import LibraryLocatorV2 | ||
|
|
||
| ContentKey = Union[LearningContextKey, UsageKey] | ||
| ContentKey = Union[LibraryLocatorV2, CourseKey, UsageKey] |
There was a problem hiding this comment.
What's wrong with leaving [LearningContextKey, UsageKey] ? Less specific?
There was a problem hiding this comment.
We need an Org to validate the permissions.
The org property is defined in the CourseKey and the LibraryLocatorV2 for our cases. For the UsageKey, we get the org from the context_key (and we also check if the contet_key is a CourseKey or LibraryLocatorV2).
We could use LearningContext and duck typing to check if we have the org field (directly in the key or in its contex_key).
I personally prefer strict typing to make sure the use conforms with what we are implementing and testing here.
I added a generic test here: 2791963 Are there some specific string keys that we can also test? |
Co-authored-by: Braden MacDonald <mail@bradenm.com>
|
@rpenido 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future. |
|
2U Release Notice: This PR has been deployed to the edX staging environment in preparation for a release to production. |
|
2U Release Notice: This PR has been deployed to the edX production environment. |
1 similar comment
|
2U Release Notice: This PR has been deployed to the edX production environment. |
Description
This PR removes the ContentObjectTag model and some overridden tagging functions from
oel_tagging.Testing instruction
Please ensure that the tests cover the expected behavior of the refactor.
Private-ref: FAL-3610