Search: Add get_es_client() to ES backend and update SignalProcessor
Review Request #15053 — Created May 13, 2026 and updated
Two targeted changes to existing files:
-ElasticsearchBackend.get_es_client()— adds a new method to the
Elasticsearch search backend that constructs and returns a raw
elasticsearch.Elasticsearchclient using the URL from the current backend
configuration. Used byFacetedSearchEngineto run direct ES queries outside
of Haystack. RaisesRuntimeErrorif the installedelasticsearchpackage
is unsupported.
-SignalProcessor— extendshandle_saveandhandle_deleteto call
FacetCache().invalidate_group(group_id)after each successful index update.
A_MODEL_TO_GROUPmapping at module level translates the model class name
to its facet cache group ID, coveringReviewRequest,User,Profile,
Group, and all comment model types (Comment,GeneralComment,
FileAttachmentComment,ScreenshotComment). Unrecognised model types are
silently ignored. Cache invalidation is skipped when the index update itself
raises an exception.
Group.usersM2M changes are also handled: when users are added to or removed
from a group the affected user records are re-indexed and the user group's
cache is invalidated.
Ran full test suite.
Verified thatget_es_clientreturns a correctly configured Elasticsearch
client and raisesRuntimeErrorfor unsupported package versions. Verified
thatSignalProcessorinvalidates the correct facet cache group after save and
delete for all tracked model types, silently ignores unrecognised models, and
does not propagate indexing exceptions. Confirmed existing backend and signal
processor behaviour is unchanged.
| Summary | ID |
|---|---|
| 171b5c9ecbfbe28250b16ea3444851fafde6a582 | |
| 66c1ba16c101b1723e3803d9784004e01ab8b6c3 | |
| 7db8aad14ac062d99258011fcaf23732b5e14caa |
| Description | From | Last Updated |
|---|---|---|
|
continuation line over-indented for visual indent Column: 36 Error code: E127 |
|
|
|
line too long (82 > 79 characters) Column: 80 Error code: E501 |
|
|
|
line too long (80 > 79 characters) Column: 80 Error code: E501 |
|
|
|
line too long (81 > 79 characters) Column: 80 Error code: E501 |
|
|
|
line too long (81 > 79 characters) Column: 80 Error code: E501 |
|
|
|
continuation line over-indented for visual indent Column: 36 Error code: E127 |
|
|
|
line too long (82 > 79 characters) Column: 80 Error code: E501 |
|
|
|
line too long (80 > 79 characters) Column: 80 Error code: E501 |
|
|
|
'reviewboard.search.testing.search_enabled' imported but unused Column: 1 Error code: F401 |
|
|
|
'reviewboard.search.search_backend_registry' imported but unused Column: 1 Error code: F401 |
|
|
|
line too long (80 > 79 characters) Column: 80 Error code: E501 |
|
|
|
'reviewboard.search.testing.search_enabled' imported but unused Column: 1 Error code: F401 |
|
|
|
'reviewboard.search.search_backend_registry' imported but unused Column: 1 Error code: F401 |
|
|
|
For dicts we also need to specify the key and value types, like dict[str, str]. Also if this dict is … |
|
|
|
This needs a type hint for the return type. |
|
|
|
This should be :py:class: instead of just :class: |
|
|
|
While you're here can you get rid of the Python 2-ism here. |
|
|
|
Let's call self.validate() here to use the full validation logic, instead of duplicating a part of it. |
|
|
|
You should be able to import everything from reviewboard.reviews.models instead of from each individual submodule. So: from reviewboard.reviews.models import ( … |
|
|
|
Can you add a "Version Added" to the docstring. |
|
|
|
You don't need the \ here, you can do: * Added handlers for :py:data:`~reviewboard.reviews.signals. comment_issue_status_updated` for each concrete comment class. |
|
|
|
We could just do self._handlers[(Review, review_published)] = self._handle_review_published There's not really a point to make a _review_signal_classes, if you add … |
|
|
|
While here can you get rid of the Python 2-ism: super().handle_save(**kwargs) |
|
|
|
Let's move this inside the try block so that if an exception happens while invalidating the cache, it gets logged. |
|
|
|
We should always use typing in any of our new code. Can you add type hints to this method and … |
|
|
|
Can you update the arguments here so that they're each on their own line. We also prefer doing ... | … |
|
|
|
Can you update the arguments here so that they're each on their own line. Also **kwargs and *args don't need … |
|
|
|
Can we be more specific about what Exception we're expecting to catch? I don't think an Exception would ever be … |
|
|
|
This should be BaseComment type instead of Any. |
|
|
|
Same here, can we be more specific about what Exception we're expecting to catch. |
|
|
|
I believe you can do self.assertSpyCalledWith( signal_processor.handle_save, instance=review_request) That should check if signal_processor.handle_save has ever been called with the review_request … |
|
|
|
ALl the methods here and below need return types. |
|
|
|
Arguments need to be on their own line. |
|
|
|
Instead of this you can use self.spy_on and kgb.SpyOpReturn to mock this. Same with the tests below. |
|
|
|
blank line contains whitespace Column: 1 Error code: W293 |
|
|
|
no newline at end of file Column: 9 Error code: W292 |
|
- Commits:
-
Summary ID Author 762404efe45048fef2306fc18fbffb4add677d0e DanielCasaresIglesias 3c33a032f9e0104d5343802ac9a0c8282894ea00 DanielCasaresIglesias
Checks run (2 succeeded)
- Change Summary:
-
Updated
signal_processor.pyto properly reindex a review request when a comment, review, issue, etc. is published on that review request.
Updated tests to ensure this is tracked correctly. - Testing Done:
-
~ Verified that
get_es_clientreturns a correctly configured Elasticsearch~ Ran full test suite.
+ Verified that get_es_clientreturns a correctly configured Elasticsearchclient and raises RuntimeErrorfor unsupported package versions. Verifiedthat SignalProcessorinvalidates the correct facet cache group after save anddelete for all tracked model types, silently ignores unrecognised models, and does not propagate indexing exceptions. Confirmed existing backend and signal processor behaviour is unchanged. - Commits:
-
Summary ID Author 3c33a032f9e0104d5343802ac9a0c8282894ea00 DanielCasaresIglesias 3343cbafea00a0aba52011b1de6ebda931f40798 Daniel Casares-Iglesias - Diff:
-
Revision 3 (+3400 -314)
Checks run (1 failed, 1 succeeded)
flake8
- Commits:
-
Summary ID 3343cbafea00a0aba52011b1de6ebda931f40798 171b5c9ecbfbe28250b16ea3444851fafde6a582 b508db3f9253792cce09ac01f3144aa0a8ef7277
- Commits:
-
Summary ID 171b5c9ecbfbe28250b16ea3444851fafde6a582 b508db3f9253792cce09ac01f3144aa0a8ef7277 171b5c9ecbfbe28250b16ea3444851fafde6a582 66c1ba16c101b1723e3803d9784004e01ab8b6c3
Checks run (2 succeeded)
-
-
-
-
Let's call
self.validate()here to use the full validation logic, instead of duplicating a part of it. -
You should be able to import everything from
reviewboard.reviews.modelsinstead of from each individual submodule. So:from reviewboard.reviews.models import ( Comment, FileAttachmentComment, GeneralComment, Group, ...) -
-
You don't need the
\here, you can do:* Added handlers for :py:data:`~reviewboard.reviews.signals. comment_issue_status_updated` for each concrete comment class. -
We could just do
self._handlers[(Review, review_published)] = self._handle_review_publishedThere's not really a point to make a
_review_signal_classes, if you add any class to the list it would always map to thereview_publishedsignal in the loop, plus I don't know what other classes you would add other than Review. -
-
Let's move this inside the try block so that if an exception happens while invalidating the cache, it gets logged.
-
We should always use typing in any of our new code. Can you add type hints to this method and every other new method you've added in this change.
You'll also need to add a
from __future__ import annotationsimport at the top of this file. -
Can we be more specific about what Exception we're expecting to catch? I don't think an Exception would ever be raised here, if there's no associated review request then
review_request = review.review_requestwould just be None (I think). -
-
I believe you can do
self.assertSpyCalledWith( signal_processor.handle_save, instance=review_request)That should check if
signal_processor.handle_savehas ever been called with the review_request passed as the instance. Same comment applies to the tests below. -
-
-
Instead of this you can use
self.spy_onandkgb.SpyOpReturnto mock this. Same with the tests below.
- Commits:
-
Summary ID 171b5c9ecbfbe28250b16ea3444851fafde6a582 66c1ba16c101b1723e3803d9784004e01ab8b6c3 171b5c9ecbfbe28250b16ea3444851fafde6a582 66c1ba16c101b1723e3803d9784004e01ab8b6c3 b5a9972985176b63976d106717cefad45e839104
- Commits:
-
Summary ID 171b5c9ecbfbe28250b16ea3444851fafde6a582 66c1ba16c101b1723e3803d9784004e01ab8b6c3 b5a9972985176b63976d106717cefad45e839104 171b5c9ecbfbe28250b16ea3444851fafde6a582 66c1ba16c101b1723e3803d9784004e01ab8b6c3 7db8aad14ac062d99258011fcaf23732b5e14caa
Checks run (2 succeeded)
-
-
For dicts we also need to specify the key and value types, like
dict[str, str]. Also if this dict is read-only in this method, then we should useMappinginstead ofdict. -
-
Can you update the arguments here so that they're each on their own line.
We also prefer doing
... | Noneinstead ofOptional[...], but here theAnytype also includes theNonetype (becauseAnymeans it can be any type). So you can remove theOptional. -
Can you update the arguments here so that they're each on their own line. Also
**kwargsand*argsdon't need type hints. So this should look likedef _handle_review_published( self, review: Review, **kwargs, ) -> None -