Get or create a fact object.

def get_or_create_fact(fact_term,
                       fact_dt_name='String',
                       fact_dt_namespace_uri=DINGOS_NAMESPACE_URI,
                       values=None,
                       value_iobject_id=None,
                       value_iobject_ts=None,
                       ):
    """
    Get or create a fact object.
    """

    if not values:
        values = []


    vocab_namespace, created = dingos_class_map['DataTypeNameSpace'].objects.get_or_create(uri=fact_dt_namespace_uri)

    fact_data_type, created = dingos_class_map['FactDataType'].objects.get_or_create(name=fact_dt_name,
                                                                                    namespace=vocab_namespace)

    # Maybe we already have a fact with exactly the same fact term and the same fact values?
    # We start by looking at the number of values

    value_objects = []

    for value in values:
        storage_location=dingos.DINGOS_VALUES_TABLE
        # collect (create or get) the required value objects
        if value == None:
            value = ''
        if isinstance(value,tuple):
            # If a value is wrapped in a tuple, the second component of the tuple
            # specifies the storage location of the value.
            value, storage_location = value

        if storage_location == dingos.DINGOS_VALUES_TABLE:
            # If the value is larger than a given size, the value is written to disk, instead.
            # We use this to keep too large values out of the database. Depending on how the
            # database is set up, this may be necessary to allow indexing, which in turn is
            # required to check uniqueness on values.

            if len(value) > dingos.DINGOS_MAX_VALUE_SIZE_WRITTEN_TO_VALUE_TABLE:
                (value_hash,storage_location) = write_large_value(value)
                value = value_hash


        fact_value, created = dingos_class_map['FactValue'].objects.get_or_create(value=value,
                                                                                 fact_data_type=fact_data_type,
                                                                                 storage_location=storage_location)
        value_objects.append(fact_value)



    # Do we already have a fact with given fact term and given values?
    #
    # For understanding the query below better, see https://groups.google.com/forum/#!topic/django-users/X9TCSrBn57Y.
    # The double query is necessary, because the first count counts the number of selected
    # fact_value objects, not the number of total objects for each fact.


    possibly_matching_facts = Fact.objects.filter(fact_values__in=value_objects,
                                                  value_iobject_id=value_iobject_id,
                                                  value_iobject_ts=value_iobject_ts,
                                                  fact_term=fact_term
    ).values_list('pk',flat=True)

    matching_facts = Fact.objects.filter(pk__in=list(possibly_matching_facts)). \
        annotate(num_values=Count('fact_values')). \
        filter(num_values=len(value_objects)). \
        exclude(id__in= \
        Fact.objects.filter(pk__in=possibly_matching_facts).annotate(total_values=Count('fact_values')). \
            filter(total_values__gt=len(value_objects)))

    # Below, for educational purposes, the original query until Dingos 0.2.0, which got *really*
    # slow with lot's of objects in the system. The reason for this are the last three lines:
    # the exclude-statement required the database to count the the number of values for each
    # Fact in the system... but we are really only interested into facts with the same
    # fact_term, value_iobject_id and value_iobject_ts...

    #matching_facts = Fact.objects.filter(fact_values__in=value_objects). \
    #    annotate(num_values=Count('fact_values')). \
    #    filter(num_values=len(value_objects)). \
    #    filter(value_iobject_id=value_iobject_id). \
    #    filter(value_iobject_ts=value_iobject_ts). \
    #    filter(fact_term=fact_term). \
    #    exclude(id__in= \
    #    Fact.objects.annotate(total_values=Count('fact_values')). \
    #        filter(total_values__gt=len(value_objects)))

    created = True
    try:
        fact_obj = matching_facts[0]
        created = False
        logger.debug("FOUND MATCHING OBJECT with pk %s" % fact_obj.pk)
    except:
        fact_obj = dingos_class_map['Fact'].objects.create(fact_term=fact_term,
                                                          value_iobject_id=value_iobject_id,
                                                          value_iobject_ts=value_iobject_ts,
                                                           )

        fact_obj.fact_values.add(*value_objects)
        fact_obj.save()


    return fact_obj, created