def bulk_perform( user, unit_set, query, target_state, add_flags, remove_flags, add_labels, remove_labels, ): matching = unit_set.search(query).prefetch() components = Component.objects.filter( id__in=matching.values_list("translation__component_id", flat=True)) target_state = int(target_state) add_flags = Flags(add_flags) remove_flags = Flags(remove_flags) updated = 0 for component in components: with transaction.atomic(), component.lock(): component.preload_sources() component.commit_pending("bulk edit", user) component_units = matching.filter( translation__component=component).select_for_update() can_edit_source = user is None or user.has_perm( "source.edit", component) update_unit_ids = [] source_units = [] for unit in component_units: changed = False source_unit = unit.source_unit if (target_state != -1 and (user is None or user.has_perm("unit.edit", unit)) and target_state != unit.state and unit.state in EDITABLE_STATES): # Create change object for edit, update is done outside the looop unit.generate_change(user, user, Change.ACTION_BULK_EDIT, check_new=False) changed = True update_unit_ids.append(unit.pk) if unit.is_source: source_units.append(unit) if can_edit_source: if add_flags or remove_flags: flags = Flags(source_unit.extra_flags) flags.merge(add_flags) flags.remove(remove_flags) new_flags = flags.format() if source_unit.extra_flags != new_flags: source_unit.is_bulk_edit = True source_unit.extra_flags = new_flags source_unit.save(update_fields=["extra_flags"]) changed = True if add_labels: source_unit.is_bulk_edit = True source_unit.labels.add(*add_labels) changed = True if remove_labels: source_unit.is_bulk_edit = True source_unit.labels.remove(*remove_labels) changed = True if changed: updated += 1 if target_state != -1: # Bulk update state Unit.objects.filter(pk__in=update_unit_ids).update( pending=True, state=target_state) # Fire source_change event in bulk for source units for unit in source_units: # The change is already done in the database, we # need it here to recalculate state of translation # units unit.is_bulk_edit = True unit.pending = True unit.state = target_state update_source(Unit, unit) component.invalidate_stats_deep() return updated
def bulk_perform( user, unit_set, query, target_state, add_flags, remove_flags, add_labels, remove_labels, project, components=None, ): matching = unit_set.search(query, distinct=False, project=project).prefetch() if components is None: components = Component.objects.filter(id__in=matching.values_list( "translation__component_id", flat=True)) target_state = int(target_state) add_flags = Flags(add_flags) remove_flags = Flags(remove_flags) update_source = add_flags or remove_flags or add_labels or remove_labels updated = 0 for component in components: component.batch_checks = True with transaction.atomic(), component.lock: component.commit_pending("bulk edit", user) component_units = matching.filter(translation__component=component) source_unit_ids = set() if target_state == -1: # Only fetch source unit ids here source_unit_ids = set( component_units.values_list("source_unit_id", flat=True)) else: update_unit_ids = [] source_units = [] # Generate changes for state change for unit in component_units.select_for_update(): source_unit_ids.add(unit.source_unit_id) if ((user is None or user.has_perm("unit.edit", unit)) and target_state != unit.state and unit.state in EDITABLE_STATES): # Create change object for edit, update is done outside the loop unit.generate_change(user, user, Change.ACTION_BULK_EDIT, check_new=False) updated += 1 update_unit_ids.append(unit.pk) if unit.is_source: source_units.append(unit) # Bulk update state Unit.objects.filter(pk__in=update_unit_ids).update( pending=True, state=target_state) # Fire source_change event in bulk for source units for unit in source_units: # The change is already done in the database, we # need it here to recalculate state of translation # units unit.is_batch_update = True unit.pending = True unit.state = target_state unit.source_unit_save() if update_source and (user is None or user.has_perm("source.edit", component)): # Perform changes on the source units source_units = (Unit.objects.filter( pk__in=source_unit_ids).prefetch().prefetch_bulk()) if add_labels or remove_labels: source_units = source_units.prefetch_related("labels") for source_unit in source_units.select_for_update(): changed = False if add_flags or remove_flags: flags = Flags(source_unit.extra_flags) flags.merge(add_flags) flags.remove(remove_flags) new_flags = flags.format() if source_unit.extra_flags != new_flags: source_unit.is_batch_update = True source_unit.extra_flags = new_flags source_unit.save(update_fields=["extra_flags"]) changed = True if add_labels: source_unit.is_batch_update = True source_unit.labels.add(*add_labels) changed = True if remove_labels: source_unit.is_batch_update = True source_unit.labels.remove(*remove_labels) changed = True if changed: updated += 1 component.invalidate_cache() component.update_source_checks() component.run_batched_checks() return updated
def add_unit( # noqa: C901 self, request, context: str, source: Union[str, List[str]], target: Optional[Union[str, List[str]]] = None, extra_flags: str = "", explanation: str = "", auto_context: bool = False, is_batch_update: bool = False, ): user = request.user if request else None component = self.component if self.is_source: translations = [self] translations.extend(component.translation_set.exclude(id=self.id)) else: translations = [component.source_translation, self] has_template = component.has_template() source_unit = None result = None # Automatic context suffix = 0 base = context while self.unit_set.filter(context=context, source=source).exists(): suffix += 1 context = f"{base}{suffix}" for translation in translations: is_source = translation.is_source kwargs = {} if has_template: kwargs["pending"] = is_source else: kwargs["pending"] = not is_source if kwargs["pending"]: kwargs["details"] = {"add_unit": True} if is_source: current_target = source kwargs["extra_flags"] = extra_flags kwargs["explanation"] = explanation else: current_target = target if current_target is None: current_target = "" if isinstance(current_target, list): current_target = join_plural(current_target) if isinstance(source, list): source = join_plural(source) if has_template: id_hash = calculate_hash(context) else: id_hash = calculate_hash(source, context) # When adding to a target the source string can already exist unit = None if not self.is_source and is_source: try: unit = translation.unit_set.get(id_hash=id_hash) flags = Flags(unit.extra_flags) flags.merge(extra_flags) new_flags = flags.format() if unit.extra_flags != new_flags or unit.explanation != explanation: unit.extra_flags = new_flags unit.explanation = explanation unit.save( update_fields=["extra_flags", "explanation"], same_content=True, ) except Unit.DoesNotExist: pass if unit is None: unit = Unit( translation=translation, context=context, source=source, target=current_target, state=STATE_TRANSLATED if bool(current_target) else STATE_EMPTY, source_unit=source_unit, id_hash=id_hash, position=0, **kwargs, ) unit.is_batch_update = is_batch_update unit.save(force_insert=True) Change.objects.create( unit=unit, action=Change.ACTION_NEW_UNIT, target=current_target, user=user, author=user, ) # The source language is always first in the translations array if source_unit is None: source_unit = unit if translation == self: result = unit if not is_batch_update: component.update_variants() component.sync_terminology() return result