def synonymize(node, into, agent): logger.info('synonymizing %s to %s', node, into) model = type(node) assert type(into) is model target = model.objects.select_for_update().get(id=into.id) assert node.definition_id == target.definition_id, "synonymizing across trees" if target.accepted_id is not None: raise BusinessRuleException('Synonymizing "{node.fullname}" to synonymized node "{into.fullname}".' .format(node=node, into=into)) node.accepted_id = target.id node.isaccepted = False node.save() if node.children.count() > 0: raise BusinessRuleException('Synonymizing node "{node.fullname}" which has children.' .format(node=node)) node.acceptedchildren.update(**{node.accepted_id_attr().replace('_id', ''): target}) #assuming synonym can't be synonymized mutation_log(TREE_SYNONYMIZE, node, agent, node.parent, [{'field_name': 'acceptedid','old_value': None, 'new_value': target.id}, {'field_name': 'isaccepted','old_value': True, 'new_value': False}]) if model._meta.db_table == 'taxon': node.determinations.update(preferredtaxon=target) from .models import Determination Determination.objects.filter(preferredtaxon=node).update(preferredtaxon=target)
def save(self, *args, **kwargs): model = type(self) self.rankid = self.definitionitem.rankid self.definition = self.definitionitem.treedef prev_self = None if self.id is None \ else model.objects.select_for_update().get(id=self.id) if prev_self is None: self.nodenumber = None self.highestchildnodenumber = None else: self.nodenumber = prev_self.nodenumber self.highestchildnodenumber = prev_self.highestchildnodenumber def save(): super(Tree, self).save(*args, **kwargs) if prev_self is None: if self.parent_id is None: # We're creating the root of a tree. # Not sure if anything else needs to be # done here, but the validation stuff won't # work so skipping it. save() return with validate_node_numbers(self._meta.db_table): adding_node(self) save() elif prev_self.parent_id != self.parent_id: with validate_node_numbers(self._meta.db_table): moving_node(self) save() else: save() try: model.objects.get(id=self.id, parent__rankid__lt=F('rankid')) except model.DoesNotExist: raise BusinessRuleException( "Tree node's parent has rank greater than itself.") if model.objects.filter(parent=self, parent__rankid__gte=F('rankid')).count() > 0: raise BusinessRuleException( "Tree node's rank is greater than some of its children.") if (prev_self is None or prev_self.name != self.name or prev_self.definitionitem_id != self.definitionitem_id or prev_self.parent_id != self.parent_id): set_fullnames(self._meta.db_table, self.definition.treedefitems.count(), self.definition.fullnamedirection == -1)
def merge(node, into): from . import models logger.info('merging %s into %s', node, into) model = type(node) assert type(into) is model target = model.objects.select_for_update().get(id=into.id) assert node.definition_id == target.definition_id, "merging across trees" if into.accepted_id is not None: raise BusinessRuleException('Merging node "{node.fullname}" with synonymized node "{into.fullname}".' .format(node=node, into=into)) target_children = target.children.select_for_update() for child in node.children.select_for_update(): matched = [target_child for target_child in target_children if child.name == target_child.name and child.rankid == target_child.rankid] if len(matched) > 0: merge(child, matched[0]) else: child.parent = target child.save() for retry in range(100): try: node.delete() return except ProtectedError as e: related_model_name, field_name = re.search(r"'(\w+)\.(\w+)'$", e.args[0]).groups() related_model = getattr(models, related_model_name) assert related_model != model or field_name != 'parent', 'children were added during merge' related_model.objects.filter(**{field_name: node}).update(**{field_name: target}) assert False, "failed to move all referrences to merged tree node"
def adding_node(node): logger.info('adding node %s', node) model = type(node) parent = model.objects.select_for_update().get(id=node.parent.id) if parent.accepted_id is not None: raise BusinessRuleException('Adding node "{node.fullname}" to synonymized parent "{parent.fullname}".' .format(node=node, parent=parent)) insertion_point = open_interval(model, parent.nodenumber, 1) node.highestchildnodenumber = node.nodenumber = insertion_point
def synonymize(node, into): logger.info('synonymizing %s to %s', node, into) model = type(node) assert type(into) is model target = model.objects.select_for_update().get(id=into.id) assert node.definition_id == target.definition_id, "synonymizing across trees" if target.accepted_id is not None: raise BusinessRuleException('Synonymizing "{node.fullname}" to synonymized node "{into.fullname}".' .format(node=node, into=into)) node.accepted_id = target.id node.isaccepted = False node.save() if node.children.count() > 0: raise BusinessRuleException('Synonymizing node "{node.fullname}" which has children.' .format(node=node)) node.acceptedchildren.update(**{node.accepted_id_attr().replace('_id', ''): target}) if model._meta.db_table == 'taxon': node.determinations.update(preferredtaxon=target) from .models import Determination Determination.objects.filter(preferredtaxon=node).update(preferredtaxon=target)
def moving_node(to_save): logger.info('moving node %s', to_save) model = type(to_save) current = model.objects.get(id=to_save.id) size = current.highestchildnodenumber - current.nodenumber + 1 new_parent = model.objects.select_for_update().get(id=to_save.parent.id) if new_parent.accepted_id is not None: raise BusinessRuleException('Moving node "{node.fullname}" to synonymized parent "parent.fullname".' .format(node=to_save, parent=new_parent)) insertion_point = open_interval(model, new_parent.nodenumber, size) # node interval will have moved if it is to the right of the insertion point # so fetch again current = model.objects.get(id=current.id) move_interval(model, current.nodenumber, current.highestchildnodenumber, insertion_point) close_interval(model, current.nodenumber, size) # update the nodenumbers in to_save so the new values are not overwritten. current = model.objects.get(id=current.id) to_save.nodenumber = current.nodenumber to_save.highestchildnodenumber = current.highestchildnodenumber