def validate_df(df, dm, con=None):
    """
    Take in a DataFrame and corresponding data model.
    Run all validations for that DataFrame.
    Output is the original DataFrame with some new columns
    that contain the validation output.
    Validation columns start with:
    presence_pass_ (checking that req'd columns are present)
    type_pass_  (checking that the data is of the correct type)
    value_pass_ (checking that the value is within the appropriate range)
    group_pass_ (making sure that group validations pass)
    """
    # check column validity
    required_one = {}  # keep track of req'd one in group validations here
    cols = df.columns
    invalid_cols = [col for col in cols if col not in dm.index]
    # go through and run all validations for the data type
    for validation_name, validation in dm.iterrows():
        value_type = validation['type']
        if validation_name in df.columns:
            output = df[validation_name].apply(test_type, args=(value_type,))
            df["type_pass" + "_" + validation_name + "_" + value_type] = output
        #
        val_list = validation['validations']
        if not val_list or isinstance(val_list, float):
            continue
        for num, val in enumerate(val_list):
            func_name, arg = split_func(val)
            if arg == "magic_table_column":
                continue
            # first validate for presence
            if func_name in presence_operations:
                func = presence_operations[func_name]
                #grade = func(validation_name, df, arg, dm)
                grade = func(validation_name, arg, dm, df, con)
                pass_col_name = "presence_pass_" + validation_name + "_" + func.__name__
                df[pass_col_name] = grade
            # then validate for correct values
            elif func_name in value_operations:
                func = value_operations[func_name]
                if validation_name in df.columns:
                    grade = df.apply(func, args=(validation_name, arg, dm, df, con), axis=1)
                    col_name = "value_pass_" + validation_name + "_" + func.__name__
                    if col_name in df.columns:
                        num_range = list(range(1, 10))
                        for num in num_range:
                            if (col_name + str(num)) in df.columns:
                                continue
                            else:
                                col_name = col_name + str(num)
                                break
                    df[col_name] = grade.astype(object)
            # last, validate at the column group level
            elif func_name in group_operations:
                func = group_operations[func_name]
                missing = func(validation_name, arg, dm, df)
                if arg not in required_one:
                    required_one[arg] = [missing]
                else:
                    required_one[arg].append(missing)
        # format the group validation columns
        for key, value in list(required_one.items()):
            if None in value:
                # this means at least one value from the required group is present,
                # so the validation passes
                continue
            else:
                # otherwise, all of the values from the required group are missing,
                # so the validation fails
                df["group_pass_{}".format(key)] = "you must have one column from group {}: {}".format(key, ", ".join(value))

    return df
Take in a DataFrame and corresponding data model.
    Run all validations for that DataFrame.
    Output is the original DataFrame with some new columns
    that contain the validation output.
    Validation columns start with:
    presence_pass_ (checking that req'd columns are present)
    type_pass_  (checking that the data is of the correct type)
    value_pass_ (checking that the value is within the appropriate range)
    group_pass_ (making sure that group validations pass)