T3206-Correct-existing-translation-data#2098
Conversation
- Update nb_translated_letters to filter on translation_status == "done" - Replace translate_date usage with translate_done for yearly stats - Add precise @api.depends on translation_status and translate_done - Ensure counters reflect only completed translations
There was a problem hiding this comment.
Code Review
This pull request updates the computed fields tracking translated letters to filter by status and date. The reviewer suggests optimizing these computations by replacing the in-memory Python filtering on the One2many field with database-level aggregations using read_group to prevent performance degradation and high memory usage.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| @api.depends("translated_letter_ids.translation_status") | ||
| def _compute_nb_translated_letters(self): | ||
| for translator in self: | ||
| translator.nb_translated_letters = len(translator.translated_letter_ids) | ||
| translator.nb_translated_letters = len( | ||
| translator.translated_letter_ids.filtered( | ||
| lambda it: it.translation_status == "done" | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Using filtered on a One2many field (translated_letter_ids) loads all associated correspondence records into memory. For translators with a large history of letters, this can cause significant performance degradation and high memory usage. Since this is a stored computed field, it will be recomputed frequently. Using read_group allows Odoo to perform a single optimized SQL aggregation query, which is much faster and avoids loading any records into memory.
@api.depends("translated_letter_ids.translation_status")
def _compute_nb_translated_letters(self):
groups = self.env["correspondence"].read_group(
[("new_translator_id", "in", self.ids), ("translation_status", "=", "done")],
["new_translator_id"],
["new_translator_id"]
)
mapped_data = {
g["new_translator_id"][0]: g["new_translator_id_count"]
for g in groups
if g["new_translator_id"]
}
for translator in self:
translator.nb_translated_letters = mapped_data.get(translator.id, 0)| @api.depends("translated_letter_ids.translation_status", "translated_letter_ids.translate_done") | ||
| def _compute_nb_translated_letters_this_year(self): | ||
| current_year = fields.Datetime.now().year | ||
| for translator in self: | ||
| translator.nb_translated_letters_this_year = len( | ||
| translator.translated_letter_ids.filtered( | ||
| lambda it: it.translate_date | ||
| and it.translate_date.year == fields.Datetime.now().year | ||
| lambda it: it.translation_status == "done" and | ||
| it.translate_done and | ||
| it.translate_done.year == current_year | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Filtering the One2many field in Python to count letters for the current year loads all correspondence records into memory. We can optimize this by using read_group with a date range filter on translate_done directly in the database query, which avoids loading records and executes a highly efficient database-level aggregation.
@api.depends("translated_letter_ids.translation_status", "translated_letter_ids.translate_done")
def _compute_nb_translated_letters_this_year(self):
current_year = fields.Datetime.now().year
groups = self.env["correspondence"].read_group(
[
("new_translator_id", "in", self.ids),
("translation_status", "=", "done"),
("translate_done", ">=", f"{current_year}-01-01 00:00:00"),
("translate_done", "<=", f"{current_year}-12-31 23:59:59"),
],
["new_translator_id"],
["new_translator_id"]
)
mapped_data = {
g["new_translator_id"][0]: g["new_translator_id_count"]
for g in groups
if g["new_translator_id"]
}
for translator in self:
translator.nb_translated_letters_this_year = mapped_data.get(translator.id, 0)| @api.depends("translated_letter_ids.translation_status", "translated_letter_ids.translate_done") | ||
| def _compute_nb_translated_letters_last_year(self): | ||
| last_year = fields.Datetime.now().year - 1 | ||
| for translator in self: | ||
| translator.nb_translated_letters_last_year = len( | ||
| translator.translated_letter_ids.filtered( | ||
| lambda it: it.translate_date | ||
| and it.translate_date.year == fields.Datetime.now().year - 1 | ||
| lambda it: it.translation_status == "done" and | ||
| it.translate_done and | ||
| it.translate_done.year == last_year | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Similar to the current year computation, filtering the One2many field in Python to count letters for the last year loads all correspondence records into memory. Using read_group with a date range filter on translate_done directly in the database query avoids loading records and executes a highly efficient database-level aggregation.
| @api.depends("translated_letter_ids.translation_status", "translated_letter_ids.translate_done") | |
| def _compute_nb_translated_letters_last_year(self): | |
| last_year = fields.Datetime.now().year - 1 | |
| for translator in self: | |
| translator.nb_translated_letters_last_year = len( | |
| translator.translated_letter_ids.filtered( | |
| lambda it: it.translate_date | |
| and it.translate_date.year == fields.Datetime.now().year - 1 | |
| lambda it: it.translation_status == "done" and | |
| it.translate_done and | |
| it.translate_done.year == last_year | |
| ) | |
| ) | |
| @api.depends("translated_letter_ids.translation_status", "translated_letter_ids.translate_done") | |
| def _compute_nb_translated_letters_last_year(self): | |
| last_year = fields.Datetime.now().year - 1 | |
| groups = self.env["correspondence"].read_group( | |
| [ | |
| ("new_translator_id", "in", self.ids), | |
| ("translation_status", "=", "done"), | |
| ("translate_done", ">=", f"{last_year}-01-01 00:00:00"), | |
| ("translate_done", "<=", f"{last_year}-12-31 23:59:59"), | |
| ], | |
| ["new_translator_id"], | |
| ["new_translator_id"] | |
| ) | |
| mapped_data = { | |
| g["new_translator_id"][0]: g["new_translator_id_count"] | |
| for g in groups | |
| if g["new_translator_id"] | |
| } | |
| for translator in self: | |
| translator.nb_translated_letters_last_year = mapped_data.get(translator.id, 0) |
Confidence Score: 3/5The translation statistics logic is narrowed correctly for normal writes, but the migration path can leave stored counters stale after upgrade.
sbc_translation/migrations/18.0.1.1.0/post-migration.py
What T-Rex did
|
NoeBerdoz
left a comment
There was a problem hiding this comment.
Thanks for your work on this! The refactor direction looks great, however, there is a key issue to fix before we can merge your proposal.
You changed the compute method, but not the existing data in the database:
Because these fields have store=True, Odoo will not automatically recompute the old rows during an upgrade. It will just keep reading the old, incorrect values. To fix this and meet the ticket requirements, we need a migration script to backfill the data and force a recompute for existing records.
Let me know if you need help setting up the migration script!
| @api.depends( | ||
| "translated_letter_ids.translation_status", | ||
| "translated_letter_ids.translate_done", | ||
| ) | ||
| @api.depends( | ||
| "translated_letter_ids.translation_status", | ||
| "translated_letter_ids.translate_done", | ||
| ) |
There was a problem hiding this comment.
Looks like a code duplication to remove
| @api.depends( | |
| "translated_letter_ids.translation_status", | |
| "translated_letter_ids.translate_done", | |
| ) | |
| @api.depends( | |
| "translated_letter_ids.translation_status", | |
| "translated_letter_ids.translate_done", | |
| ) | |
| @api.depends( | |
| "translated_letter_ids.translation_status", | |
| "translated_letter_ids.translate_done", | |
| ) |
…nslated by the translators
| translators.flush_model([ | ||
| "nb_translated_letters", | ||
| "nb_translated_letters_this_year", | ||
| "nb_translated_letters_last_year" | ||
| ]) No newline at end of file |
There was a problem hiding this comment.
When this migration runs, env.add_to_compute() only queues these stored fields for recomputation; flush_model() flushes cached values but does not drain the recompute queue. Existing translation.user rows can keep their old inflated counters after upgrade, so the dashboard API continues to return the pre-change totals until another write happens to trigger a recompute.
refactor: count only completed translations in stats