def validate_component(component):
    is_valid = validate_component_locally(component)

    for child_component in component.child_components.all():
        is_valid = validate_component(child_component) and is_valid

    for variable in component.variables.all():
        is_valid = validate_variable(variable) and is_valid

    for reset in component.resets.all():
        is_valid = validate_reset(reset) and is_valid

    for math in component.maths.all():
        is_valid = validate_math(math) and is_valid

    error_tree, error_count = draw_error_tree(component)
    error_count += component.errors.count()
    component.error_tree = {
        'tree_html': error_tree,
        'error_count': error_count
    }
    component.is_valid = is_valid
    component.save()

    return is_valid
def validate_cellmodel(model):
    is_valid = validate_cellmodel_locally(model)

    for component in model.all_components.all():
        is_valid = validate_component(component) and is_valid

    for compoundunit in model.compoundunits.all():
        is_valid = validate_compoundunit(compoundunit) and is_valid

    # Validate connections and equivalent variable networks in the model
    is_valid = validate_connections(model) and is_valid

    error_tree, error_count = draw_error_tree(model)
    error_count += model.errors.count()
    model.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    model.is_valid = is_valid
    model.save()

    return is_valid
def validate_compoundunit(cu):
    for e in cu.errors.all():
        e.delete()

    # Built-in units are valid
    if cu.is_standard:
        return True

    cu.error_tree = None

    # Checking the name
    is_valid, hints = is_cellml_identifier(cu.name)
    if not is_valid:
        err = ItemError(
            hints="Invalid compound_units name <i>{n}</i>: {h}".format(
                n=cu.name, h=hints),
            spec='8.1.1',
            fields=['name'])
        err.save()
        cu.errors.add(err)

    if CompoundUnit.objects.filter(name=cu.name, is_standard=True).count() > 0:
        err = ItemError(
            hints=
            "The name cannot be the same as a built-in units name, <i>{}</i>".
            format(cu.name),
            spec="8.1.3",
            fields=['name'])
        err.save()
        cu.errors.add(err)
        is_valid = False

    # Also need to check the unit elements of this compound unit here as they can't be accessed individually
    for u in cu.product_of.all():
        is_valid = validate_unit(u) and is_valid

    error_tree, error_count = draw_error_tree(cu)
    error_count += cu.errors.count()
    cu.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    cu.is_valid = is_valid
    cu.save()

    return is_valid
def validate_unit(unit):
    is_valid = True
    for e in unit.errors.all():
        e.delete()
    unit.error_tree = None

    # 9.1.1 Check that the pointers are valid
    if not unit.child_cu:
        is_valid = False
        err = ItemError(
            hints="Unit in units <i>{p}</i> points to a blank unit".format(
                p=unit.parent_cu.name),
            spec='9.1.1',
            fields=['child_cu'])
        err.save()
        unit.parent_cu.errors.add(err)

    error_tree, error_count = draw_error_tree(unit)
    error_count += unit.errors.count()
    unit.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    unit.is_valid = is_valid
    unit.save()

    return is_valid
def validate_reset(reset):
    is_valid = True
    for e in reset.errors.all():
        e.delete()

    if reset.order == '' or reset.order is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not have an order set".format(
                r=reset.name),
            spec="12.1.2",
            fields=["order"])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.test_value is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a test_value".format(
                r=reset.name),
            spec="12",  # TODO find correct code for resets
            fields=['test_value'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.reset_value is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a reset_value".format(
                r=reset.name),
            spec="12",  # TODO find correct code for resets
            fields=['reset_value'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.component is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not have a component".format(
                r=reset.name),
            spec="10.1.2.2",
            fields=['component'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.variable is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a variable".format(
                r=reset.name),
            spec="12.1.1",
            fields=['variable'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.test_variable is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a test_variable".format(
                r=reset.name),
            spec="12",  # TODO find correct code for resets
            fields=['test_variable'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.component is not None:
        if reset.variable is not None and reset.component != reset.variable.component:
            err = ItemError(
                hints=
                "Reset <i>{r}</i> in component <i>{c}</i> refers to a variable <i>{v}</i> which is in a "
                "different component, <i>{vc}</i>".format(
                    r=reset.name,
                    c=reset.component.name,
                    v=reset.variable.name,
                    vc=reset.variable.component.name),
                spec="12",  # TODO find correct code for resets
                fields=['variable'])
            err.save()
            reset.errors.add(err)
            is_valid = False

        if reset.test_variable is not None and reset.component != reset.test_variable.component:
            err = ItemError(
                hints=
                "Reset <i>{r}</i> in component <i>{c}</i> refers to a test_variable <i>{v}</i> in a different "
                "component, <i>{vc}</i>".format(
                    r=reset.name,
                    c=reset.component.name,
                    v=reset.test_variable.name,
                    vc=reset.test_variable.component.name),
                spec="12",  # TODO find correct code for resets
                fields=['test_variable'])
            err.save()
            reset.errors.add(err)
            is_valid = False

    error_tree, error_count = draw_error_tree(reset)
    error_count += reset.errors.count()
    reset.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    reset.is_valid = is_valid
    reset.save()

    return is_valid
def validate_variable(variable):
    # Variables are all local validation - no need for a duplicate
    for e in variable.errors.all():
        e.delete()

    try:
        initial_component_id = variable.initial_value_variable.component.id
    except Exception as e:
        initial_component_id = None
    try:
        initial_unit_id = variable.initial_value_variable.compoundunit.id
    except Exception as e:
        initial_unit_id = None

    # Validate name
    is_valid, hints = is_cellml_identifier(variable.name)
    if not is_valid:
        err = ItemError(hints="Invalid variable name <i>{n}</i>: {h}".format(
            n=variable.name, h=hints),
                        spec='11.1.1.1',
                        fields=["name"])
        err.save()
        variable.errors.add(err)

    # Check that variable has units
    if variable.compoundunit is None:
        err = ItemError(
            hints="Variable <i>{}</i> does not have any units.".format(
                variable.name),
            spec="11.1.1.2",
            fields=["compoundunit"])
        err.save()
        variable.errors.add(err)
        is_valid = False
    # Check that the units of the initialising variable are the same as for this variable
    elif initial_unit_id is not None:
        if initial_unit_id != variable.compoundunit.id:
            err = ItemError(
                hints=
                "Variable has units of <i>{u}</i> but is initialised by variable <i>{vi}</i> "
                "which has units of <i>{ui}</i>.".format(
                    u=variable.compoundunit.name,
                    vi=variable.initial_value_variable.name,
                    ui=variable.initial_value_variable.compoundunit.name),
                spec="11?",
                fields=["initial_value_variable"])
            err.save()
            variable.errors.add(err)
            is_valid = False

    # Check that the variable has access to the initialising variable
    if initial_component_id is not None:
        if initial_component_id != variable.component.id:
            err = ItemError(
                hints=
                "Variable <i>{v}</i> in component <i>{c}</i> is initialised by variable <i>{vi}</i> "
                "which is in another component <i>{ci}</i>.".format(
                    v=variable.name,
                    c=variable.component.name,
                    vi=variable.initial_value_variable.name,
                    ci=variable.initial_value_variable.component.name),
                spec="11.1.2.1",
                fields=['initial_value_variable', 'component'])
            err.save()
            variable.errors.add(err)
            is_valid = False

    # Check that there is only one method of initialisation
    if variable.initial_value_variable is not None and variable.initial_value_constant is not None:
        err = ItemError(
            hints=
            "Variable <i>{v}</i> in component <i>{c}</i> is initialised by variable <i>{vi}</i> "
            "as well as by the constant value <i>{ci}</i>. There can be only one."
            .format(v=variable.name,
                    c=variable.component.name,
                    vi=variable.initial_value_variable.name,
                    ci=variable.initial_value_constant),
            spec="11.1.2.1",
            fields=['initial_value_variable', 'initial_value_constant'])
        err.save()
        variable.errors.add(err)
        is_valid = False

    # Check that directly related resets have unique orders
    duplicated_orders = [
        x[0] for x in variable.reset_variables.values_list('order')
    ]
    unique_orders = list(set(duplicated_orders))
    for x in unique_orders:
        duplicated_orders.remove(x)

    for rv in variable.reset_variables.filter(order__in=duplicated_orders):
        err = ItemError(
            hints=
            "Variable <i>{v}</i> in component <i>{c}</i> contains more than one reset with order of "
            "<i>{o}</i>.".format(v=variable.name,
                                 c=variable.component.name,
                                 o=rv.order),
            spec="??",  # TODO find spec reference for resets
            fields=['reset_variables']  # TODO not sure what this should be
        )
        err.save()
        variable.errors.add(err)
        is_valid = False

    error_tree, error_count = draw_error_tree(variable)
    error_count += variable.errors.count()
    variable.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    variable.is_valid = is_valid
    variable.save()

    return is_valid