Ejemplo n.º 1
0
def resolve_field_of_path(manager, field_path, return_related_models=False):
    """
        Resolves a field for a manager and field_path that might have __'s indicating relations
    :param manager:
    :param field_path:
    :param return_related_models
    :return: Either the resolved field, or if return_related_models is True this
    instead returns a tuple of the resolve field and the related model, the latter being non-null
    only for foreign key properties or id properties that represent foreign keys
    (e.g. built_form or built_form_id, respectively, would return the tuple
    (ForeignKey, BuiltForm) or (AutoField, BuiltForm), respectively
    """
    parts = field_path.split('__')
    model = manager.model
    related_model = None
    while len(parts) > 0:
        part = parts.pop(0)
        # Each part either results in a model or field. Field is the end-case
        # Work around Django init bug for dynamic models
        clear_many_cache(model)
        model_or_field_tuple = model._meta._name_map.get(part)
        if not model_or_field_tuple:
            # It might be that the part represents the attname of a field, not the field name
            # This is true for ForeignKey relationships
            # Find the matching field, and if found find the related model's id field
            related_field_tuple = get_value_of_first_matching_key(
                lambda name, field: part == field[0].attname
                if hasattr(field[0], 'attname') else False,
                model._meta._name_map)
            if related_field_tuple:
                model_or_field_tuple = related_field_tuple[
                    0].rel.to._meta._name_map['id']
                related_model = related_field_tuple[0].rel.to
            if not model_or_field_tuple:
                # Something went wrong, give up
                return None
        if isinstance(model_or_field_tuple[0], RelatedField):
            # RelatedField. Recurse unless we are out of parts of the field_path
            if len(parts) > 0:
                # Continue on
                model = model_or_field_tuple[0].rel.to
                continue
            else:
                related_model = model_or_field_tuple[0].rel.to
        elif isinstance(model_or_field_tuple[0], RelatedObject):
            # RelatedObject. Always recurse
            model = model_or_field_tuple[0].model
            continue

        # RelatedField with no parts left or simple Field type
        return model_or_field_tuple[0] if not return_related_models else\
        (model_or_field_tuple[0], related_model or None)
Ejemplo n.º 2
0
def resolve_field_of_path(manager, field_path, return_related_models=False):
    """
        Resolves a field for a manager and field_path that might have __'s indicating relations
    :param manager:
    :param field_path:
    :param return_related_models
    :return: Either the resolved field, or if return_related_models is True this
    instead returns a tuple of the resolve field and the related model, the latter being non-null
    only for foreign key properties or id properties that represent foreign keys
    (e.g. built_form or built_form_id, respectively, would return the tuple
    (ForeignKey, BuiltForm) or (AutoField, BuiltForm), respectively
    """
    parts = field_path.split('__')
    model = manager.model
    related_model = None
    while len(parts) > 0:
        part = parts.pop(0)
        # Each part either results in a model or field. Field is the end-case
        # Work around Django init bug for dynamic models
        clear_many_cache(model)
        model_or_field_tuple = model._meta._name_map.get(part)
        if not model_or_field_tuple:
            # It might be that the part represents the attname of a field, not the field name
            # This is true for ForeignKey relationships
            # Find the matching field, and if found find the related model's id field
            related_field_tuple = get_value_of_first_matching_key(
                lambda name, field: part == field[0].attname if hasattr(field[0], 'attname') else False,
                model._meta._name_map)
            if related_field_tuple:
                model_or_field_tuple = related_field_tuple[0].rel.to._meta._name_map['id']
                related_model = related_field_tuple[0].rel.to
            if not model_or_field_tuple:
                # Something went wrong, give up
                return None
        if isinstance(model_or_field_tuple[0], RelatedField):
            # RelatedField. Recurse unless we are out of parts of the field_path
            if len(parts) > 0:
                # Continue on
                model = model_or_field_tuple[0].rel.to
                continue
            else:
                related_model = model_or_field_tuple[0].rel.to
        elif isinstance(model_or_field_tuple[0], RelatedObject):
            # RelatedObject. Always recurse
            model = model_or_field_tuple[0].model
            continue

        # RelatedField with no parts left or simple Field type
        return model_or_field_tuple[0] if not return_related_models else\
        (model_or_field_tuple[0], related_model or None)
Ejemplo n.º 3
0
    def resolve_related_model_pk(db_entity_key):
        related_model = config_entity.db_entity_feature_class(db_entity_key)
        # The common Geography class
        geography_class = feature_class_creator.common_geography_class(related_model)
        geography_scope = feature_class_creator.common_geography_scope(related_model)
        logger.warn("Resolved geography scope %s", geography_scope)
        # Find the geographies ManyToMany fields that relates this model to the related_model
        # via a Geography class. Which geography class depends on their common geography scope
        geographies_field = feature_class_creator.geographies_field(geography_scope)
        try:
            # Find the queryable field name from the geography class to the related model
            related_model_geographies_field_name = resolve_queryable_name_of_type(geography_class, related_model)
        except:
            # Sometimes the geography class hasn't had its fields cached properly. Fix here
            clear_many_cache(geography_class)
            related_model_geographies_field_name = resolve_queryable_name_of_type(geography_class, related_model)

        return "%s__%s__pk" % (geographies_field.name, related_model_geographies_field_name)
Ejemplo n.º 4
0
    def resolve_related_model_pk(db_entity_key):
        related_model = config_entity.db_entity_feature_class(db_entity_key)
        # The common Geography class
        geography_class = feature_class_creator.common_geography_class(related_model)
        geography_scope = feature_class_creator.common_geography_scope(related_model)
        logger.warn("Resolved geography scope %s", geography_scope)
        # Find the geographies ManyToMany fields that relates this model to the related_model
        # via a Geography class. Which geography class depends on their common geography scope
        geographies_field = feature_class_creator.geographies_field(geography_scope)
        try:
            # Find the queryable field name from the geography class to the related model
            related_model_geographies_field_name = resolve_queryable_name_of_type(geography_class, related_model)
        except:
            # Sometimes the geography class hasn't had its fields cached properly. Fix here
            clear_many_cache(geography_class)
            related_model_geographies_field_name = resolve_queryable_name_of_type(geography_class, related_model)

        return '%s__%s__pk' % (geographies_field.name, related_model_geographies_field_name)
Ejemplo n.º 5
0
    def related_resource_field(cls, related_field_name, relationship_dict, no_cache=False):
        """
           Returns a tuple of the related_field resource name and resource field. ManyToMany and ForeignKey is currently supported
        :param related_field_name: The related name on the model class
        :param relationship_dict: A dict describing the relationship with keys
            descriptor: The related ReverseSingleRelatedObjectDescriptor (or similar) of the model class. Expected is an instance of models.ManyToMany, or models.ForeignKey
            field: (Alternative to descriptor. A related Field
            full: Tastypie field param
            null: Tastypie field param
            no_cache: Don't use a cached resource for this field's dynamic resource class
            use_version_fields: Explained below
        :param no_cache: Default False. True to avoid using a cached resource class
        :return: A tuple with the field.name or singularized version and the created resource field.
        All fields created have full=False and null=True with the assumption that the value is optional and a need not be nested
        """

        cls.logger.info("Resolving related resource field with name %s and no_cache %s" % (related_field_name, no_cache))
        callable_attribute = None
        full = None
        null = True
        use_version_fields = False
        if isinstance(relationship_dict, Field):
            # related_descriptor is a Field
            related_field = relationship_dict
        elif isinstance(relationship_dict, dict):
            related_field = relationship_dict['descriptor'].field if\
                relationship_dict.get('descriptor') else\
                relationship_dict['field']
            callable_attribute = relationship_dict.get('callable_attribute')
            full = relationship_dict.get('full', False)
            null = relationship_dict.get('null', True)
            no_cache = relationship_dict.get('no_cache', True)
            # Enable lookup of this field and the resource fields of Revisionable._version_field_dict
            use_version_fields = relationship_dict.get('use_version_fields', False)
        else:
            # ReverseManyToMany and similar
            related_field = relationship_dict.field
        related_resource_class = FootprintResource.resolve_resource_class(
            related_field.rel.to,
            no_cache=no_cache,
            use_version_fields=use_version_fields,
            only_abstract_resources=False)
        # Clear the related object field caches in case it didn't pick up the dynamic many-to-many field of cls's model
        clear_many_cache(related_field.rel.to)
        name = related_field.name or related_field_name
        if callable_attribute:
            # Explicit callable_attribute defined for this attribute
            attribute = callable_attribute
        elif use_version_fields:
            # If this is set it means we set the Revisionable._version_field_dict
            # Tell the related field to look for a _version_field_dict key matching the attribute name
            # If found return that, otherwise just return the normal object field value
            def get_attribute(bundle):
                obj = (bundle.obj._version_field_dict if
                                        hasattr(bundle.obj, '_version_field_dict') else
                                        dict())\
                    .get(name, getattr(bundle.obj, name))
                return obj
            attribute = get_attribute
        else:
            # Simply use the field name as the attribute
            attribute = name

        if isinstance(related_field, models.ManyToManyField):
            field_class = fields.ToManyField
        elif isinstance(related_field, models.ForeignKey):
            field_class = fields.ToOneField
        else:
            raise Exception("Expected ToManyField or ToOneField. Got %s" % related_field.__class__.__name__)
        return [name, field_class(related_resource_class, attribute=attribute, full=full, null=null)]
Ejemplo n.º 6
0
    def related_resource_field(cls, related_field_name, relationship_dict, no_cache=False):
        """
           Returns a tuple of the related_field resource name and resource field. ManyToMany and ForeignKey is currently supported
        :param related_field_name: The related name on the model class
        :param relationship_dict: A dict describing the relationship with keys
            descriptor: The related ReverseSingleRelatedObjectDescriptor (or similar) of the model class. Expected is an instance of models.ManyToMany, or models.ForeignKey
            field: (Alternative to descriptor. A related Field
            full: Tastypie field param
            null: Tastypie field param
            no_cache: Don't use a cached resource for this field's dynamic resource class
            use_version_fields: Explained below
        :param no_cache: Default False. True to avoid using a cached resource class
        :return: A tuple with the field.name or singularized version and the created resource field.
        All fields created have full=False and null=True with the assumption that the value is optional and a need not be nested
        """

        cls.logger.info("Resolving related resource field with name %s and no_cache %s" % (related_field_name, no_cache))
        callable_attribute = None
        full = None
        null = True
        use_version_fields = False
        if isinstance(relationship_dict, Field):
            # related_descriptor is a Field
            related_field = relationship_dict
        elif isinstance(relationship_dict, dict):
            related_field = relationship_dict['descriptor'].field if\
                relationship_dict.get('descriptor') else\
                relationship_dict['field']
            callable_attribute = relationship_dict.get('callable_attribute')
            full = relationship_dict.get('full', False)
            null = relationship_dict.get('null', True)
            no_cache = relationship_dict.get('no_cache', True)
            # Enable lookup of this field and the resource fields of Revisionable._version_field_dict
            use_version_fields = relationship_dict.get('use_version_fields', False)
        else:
            # ReverseManyToMany and similar
            related_field = relationship_dict.field
        related_resource_class = FootprintResource.resolve_resource_class(
            related_field.rel.to,
            no_cache=no_cache,
            use_version_fields=use_version_fields,
            only_abstract_resources=False)
        # Clear the related object field caches in case it didn't pick up the dynamic many-to-many field of cls's model
        clear_many_cache(related_field.rel.to)
        name = related_field.name or related_field_name
        if callable_attribute:
            # Explicit callable_attribute defined for this attribute
            attribute = callable_attribute
        elif use_version_fields:
            # If this is set it means we set the Revisionable._version_field_dict
            # Tell the related field to look for a _version_field_dict key matching the attribute name
            # If found return that, otherwise just return the normal object field value
            def get_attribute(bundle):
                obj = (bundle.obj._version_field_dict if
                                        hasattr(bundle.obj, '_version_field_dict') else
                                        dict())\
                    .get(name, getattr(bundle.obj, name))
                return obj
            attribute = get_attribute
        else:
            # Simply use the field name as the attribute
            attribute = name

        if isinstance(related_field, models.ManyToManyField):
            field_class = fields.ToManyField
        elif isinstance(related_field, models.ForeignKey):
            field_class = fields.ToOneField
        else:
            raise Exception("Expected ToManyField or ToOneField. Got %s" % related_field.__class__.__name__)
        return [name, field_class(related_resource_class, attribute=attribute, full=full, null=null)]