Autocomplete tags API function - #59
Conversation
|
Thanks for the pull request, @ChrisChV! 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. |
0b9e6f6 to
e8f8244
Compare
0642778 to
4923b66
Compare
|
@pomegranited The CI tests are failing, but in my local works normally. Please let me know your results in your local |
@ChrisChV Hmm.. If you merge my latest changes, it should fix it. |
4923b66 to
67bb621
Compare
@pomegranited Thanks. MySQL uses |
|
@ormsbee It's ready for your review |
89e6455 to
2815e58
Compare
pomegranited
left a comment
There was a problem hiding this comment.
Couple of minor nits, but I've also got a question about duplicate Tags in a single taxonomy?
ormsbee
left a comment
There was a problem hiding this comment.
Mostly questions and requests around the database queries being made. Thank you!
| .order_by('_value') | ||
| # obtain the values of the tags | ||
| .values_list('_value', flat=True) | ||
| # remove repeats |
There was a problem hiding this comment.
In what scenarios could we get repeats?
There was a problem hiding this comment.
About the free-form taxonomies, we do the search in ObjectTag. There may be the scenario where different objects have the same tag
| [:count] | ||
| ) | ||
| # Build tag results | ||
| result = [TagResult(id=None, value=value) for value in result] |
There was a problem hiding this comment.
Given that this is likely to be used by a REST API at some point, I think it's fine to have a function that returns a QuerySet, even if this function just calls out to it and does this serialization into TagResult.
There was a problem hiding this comment.
You are right. I have added a separate function for serialization, that can be used by other internal functions
| # omit any tags applied to the given object | ||
| .exclude(object_id=object_id) | ||
| # omit any free-text tags from other objects whose values match the tags on the given object | ||
| .exclude(_value__in=excluded_tags) |
There was a problem hiding this comment.
If we're omitting everything with the object_id here, why do we need to check excluded_tags as well (which should just be a subset)?
There was a problem hiding this comment.
It's the same logic of #59 (comment). There may be tags of other objects that the object_id already has. But I have realized that .exclude(object_id=object_id) is no necessary, because .exclude(_value__in=excluded_tags) also excludes the tags of object_id
|
Hi @ChrisChV! Just following up on this :) |
|
Hi @mphilbrick211 :) This is blocked on #62, but we're nearly there :) |
Thanks, @pomegranited! |
adb3e5a to
507848b
Compare
|
|
||
| def autocomplete_tags( | ||
| self, prefix: str, object_id: str = None, count=10 | ||
| ) -> models.QuerySet: |
There was a problem hiding this comment.
I think it's going to be terribly confusing that the QuerySet returned here is a ObjectTag queryset for free-text taxonomies, but a Tag queryset for closed taxonomies. So we can't simply use the API and treat them like the same thing.
Plus, for closed taxonomies, the frontend can handle auto-completion without consulting the backend (e.g using autocompleter), because it's already fetched all of the Tags in a Taxonomy to display the tree of options:
So I think we should:
- Only autocomplete from existing ObjectTags, ignoring the free-text vs closed taxonomy distinction.
- Match
textanywhere in the value, not justprefix(as shown above), by using.filter(_value__icontains=text)instead - Remove the
countparameter -- the REST API that ends up calling this can handle pagination from the returned query set.
Does that sound reasonable to you?
There was a problem hiding this comment.
@pomegranited Yes, with this the function will be cleaner. The new function will only return the _value and tag. The second will be null for closed taxonomies
With this change, I wonder, won't there be other things that the frontend can simplify or change?
pomegranited
left a comment
There was a problem hiding this comment.
This is looking really solid, @ChrisChV .
I've suggested some improvements to the docstrings and an alias to make the returned tag values nicer, but will approve once they're applied.
There was a problem hiding this comment.
LGTM.
While I generally recommend against returning QuerySets in api.py methods*, I think this one makes sense because it's a ValuesQuerySet. I like how the method is returning values and tag, so that we can be more flexible in the future, e.g. returning users even if their Tags don't exist yet.
I have a few questions/suggestions, but nothing blocking.
Nice work.
* Because: "An app’s Django models and other internal data structures should not be exposed via its Python APIs"
Huh ok.. I didn't know that, and we definitely should have picked this up in the design phase. My assumption was that the REST APIs should be as think as possible, and so should use the Python APIs (and django model rules) to encapsulate any complexity. And since we use DRF, that means preferring the Python APIs to return QuerySets. How should this be done instead? |
To return a list of TagResult containing the id and value of the tags.
We need to use __istartswith to make a case-insensitive query on MySQL
- Match text anywhere in the value - Only autocomplete from existing ObjectTags, ignoring the free-text vs closed taxonomy distinction. - Remove the count parameter - Refactoring tests to use ddt
|
@pomegranited It's not a hard and fast rule. The main problem the authors of that OEP are trying to avoid is that Django model instances are very "powerful" and once you have access to one, you can make a lot of "internal" changes that may violate the assumptions of the API - for example, you can change fields and save() it, bypassing whatever So: How can you make the REST APIs a thin wrapper around the python API, without using Django models? You can look at this example in content_libraries v2 as one example I'm familiar with. Other times it may make more sense to use a query set in the REST API if it makes your life a lot easier (e.g. pagination). Yet another option is have an "internal API" that uses QuerySets and then your REST API can wrap it directly and your Python API can wrap it and convert the Django objects to non-Django data structures. As far as I know, any of those are totally fine, so just go with any approach that feels clean to you. Just keep in mind that exposing Django model instances via python will tempt people to bypass your python API, which you probably want to discourage. |
7c30263 to
ea01201
Compare
@pomegranited Done 👍 |
pomegranited
left a comment
There was a problem hiding this comment.
👍 Thank you @ChrisChV !
- I tested this by checking that the tests correctly cover the expected behavioral use cases.
- I read through the code.
-
I checked for accessibility issuesN/A - Includes documentation -- docstrings
|
Hi @ormsbee -- have your concerns been addressed? Github won't let me merge until you say it's ok :) |
I don't want to "dismiss" this review -- Dave's questions were very relevant and appreciated. But they've been answered, and we had a second approval from Braden, so I think it's safe to merge this.
|
@ChrisChV 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future. |

Description
Implements autocomplete_tags function
Supporting information
Merge after #57.
Closes openedx/modular-learning#65
Testing instructions