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)
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)
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)
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)
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)]