def feature_behavior_from_behavior_template(self): """ The method to create a FeatureBehavior from the template_feature_behavior. This might be a subclass instance of FeatureBehavior and/or preconfigured values Override FeatureBehavior.set_defaults to set preconfigured values """ template_feature_behavior = self.subclassed_template_feature_behavior # Clone it, making a new intersection instance by removing the id # If the FeatureBehavior has no Intersection get it from the Behavior # Some behaviors, like background_imagery and result also have no Intersection intersection = template_feature_behavior.intersection_subclassed or self.intersection_subclassed if not intersection: raise Exception( "No intersection for the template FeatureBehavior resolved") intersection.id = 0 logger.debug( "Intersection from template is %s of subclass %s" % (model_dict(intersection, include_primary_key=True) if intersection else 'None', intersection.__class__ if intersection else 'None')) feature_behavior = template_feature_behavior.__class__(**merge( model_dict(template_feature_behavior), dict(behavior=self, is_template=False, intersection=intersection))) # Set any defaults defined by the base or subclass feature_behavior.set_defaults() return feature_behavior
def update_or_create_layer_style(layer_style_configuration, style_key, existing_layer_style=None): """ Creates a LayerStyle the StyleAttribute for cartocss styling. A Template is not specific to a Layer instance, rather specific to underlying Feature clss of the Layer :param layer_style_configuration: A dict() containing values specific to the model (i.e. the Feature subclass). This will be merged with layer_style that matches the subclass and 0 or more attributes of the Feature subclass that the Layer represents. :param style_key: The unique key of the template. Use the same key to share the template among instances :param existing_layer_style. The optional layer style class upon which the template key is named. If this is omitted, a generic template is created that doesn't load any predefined style info from the system. TODO There is no particular purpose for a Template based on only a db_entity_key at this point. The one based on a styled_class (Feature class) can load the template matching the class and attributes to provide default styling for the layer. We might have a case for having various generic default styling for a layer that is not based on a feature_class. :return: """ logger.info("existing_layer_style %s" % model_dict(existing_layer_style)) layer_style, created, updated = LayerStyle.objects.update_or_create( key=style_key, defaults=merge( # look first for whether the layer style exists and update otherwise create it model_dict(existing_layer_style) if existing_layer_style else dict(), dict( name=style_key, content_type=ContentTypeKey.CSS, # This represents the master version of the LayerStyle and will not change # unless the backend configuration is updated geometry_type=layer_style_configuration.get('geometry_type'), html_class=style_key))) # iterate over the configured style attribtues and update or create new instances and cartocss for style_attribute_config in layer_style_configuration.get( 'style_attributes') or []: style_attribute, created, updated = StyleAttribute.objects.update_or_create( key=style_attribute_config.get('key') if style_attribute_config.get('key') else style_key + "__default", defaults=dict(name=style_attribute_config.get('name') if style_attribute_config.get('name') else 'Default', attribute=style_attribute_config.get('attribute'), style_type=style_attribute_config.get('style_type'), opacity=style_attribute_config.get('opacity') or 1, style_value_contexts=style_attribute_config.get( 'style_value_contexts'))) layer_style.style_attributes.add(style_attribute) layer_style.save() return layer_style
def update_or_create_layer_style(layer_style_configuration, style_key, existing_layer_style=None): """ Creates a LayerStyle the StyleAttribute for cartocss styling. A Template is not specific to a Layer instance, rather specific to underlying Feature clss of the Layer :param layer_style_configuration: A dict() containing values specific to the model (i.e. the Feature subclass). This will be merged with layer_style that matches the subclass and 0 or more attributes of the Feature subclass that the Layer represents. :param style_key: The unique key of the template. Use the same key to share the template among instances :param existing_layer_style. The optional layer style class upon which the template key is named. If this is omitted, a generic template is created that doesn't load any predefined style info from the system. TODO There is no particular purpose for a Template based on only a db_entity_key at this point. The one based on a styled_class (Feature class) can load the template matching the class and attributes to provide default styling for the layer. We might have a case for having various generic default styling for a layer that is not based on a feature_class. :return: """ logger.info("existing_layer_style %s" % model_dict(existing_layer_style)) layer_style, created, updated = LayerStyle.objects.update_or_create( key=style_key, defaults=merge( # look first for whether the layer style exists and update otherwise create it model_dict(existing_layer_style) if existing_layer_style else dict(), dict( name=style_key, content_type=ContentTypeKey.CSS, # This represents the master version of the LayerStyle and will not change # unless the backend configuration is updated geometry_type=layer_style_configuration.get('geometry_type'), html_class=style_key ) ) ) # iterate over the configured style attribtues and update or create new instances and cartocss for style_attribute_config in layer_style_configuration.get('style_attributes') or []: style_attribute, created, updated = StyleAttribute.objects.update_or_create( key=style_attribute_config.get('key') if style_attribute_config.get('key') else style_key + "__default", defaults=dict( name=style_attribute_config.get('name') if style_attribute_config.get('name') else 'Default', attribute=style_attribute_config.get('attribute'), style_type=style_attribute_config.get('style_type'), opacity=style_attribute_config.get('opacity') or 1, style_value_contexts=style_attribute_config.get('style_value_contexts') ) ) layer_style.style_attributes.add(style_attribute) layer_style.save() return layer_style
def update_or_create_associations(self, behavior): # Update/Create template_feature_behavior and set it # This is done after the update because the template references back to the Behavior self.template_feature_behavior = self.update_or_create_template_feature_behavior(behavior._template_feature_behavior) self.name = titleize(BehaviorKey.Fab.remove(self.key)) self.save() # Handle the manys that were embedded in the instance constructor # _parents are always configured without the BehaviorKey prefix if behavior._parents: self.parents.clear() self.parents.add(*Behavior.objects.filter(key__in=map(lambda parent: BehaviorKey.Fab.ricate(parent), behavior._parents))) if behavior._tags: self.tags.clear() self.tags.add(*map(lambda tag: Tag.objects.update_or_create(tag=tag.tag)[0], behavior._tags)) # Add the a default tag that matches the key of the Behavior tag_key = BehaviorKey.Fab.remove(self.key) if not self.tags.filter(tag=tag_key): self.tags.add(Tag.objects.update_or_create(tag=tag_key)[0]) # Inherit the intersection from the first parent if we don't have one # Default to a polygon to polygon GeographicIntersection intersection = behavior._intersection or\ (self.parents.all()[0].intersection_subclassed if\ self.parents.count() == 1 else\ GeographicIntersection.polygon_to_polygon) if not intersection: raise Exception("All behaviors must have a default intersection") # Use all attributes to find/create the Intersection instance, including nulls # Behaviors can't have specific Intersection properties, as FeatureBehaviors can, so it's safe to # share Intersection instances among them. The Intersection of a Behavior is really just the template # for that of a FeatureBehavior self.intersection = intersection.__class__.objects.get_or_create(**model_dict(intersection))[0] self.save()
def update_or_create_attribute_group(attribute_group): """ Update or create a behavior based on the given behavior """ return AttributeGroup.objects.update_or_create( key=attribute_group.key, defaults=model_dict(attribute_group, omit_fields=['key']) )[0]
def update_layer_of_scenario(scenario): logger.info("Updating layer of db_entity_key %s, Scenario %s" % (db_entity_key, scenario.name)) db_entity_interest = DbEntityInterest.objects.get(config_entity=scenario, db_entity__key=db_entity_key) return Layer.objects.update_or_create( db_entity_interest=db_entity_interest, defaults=merge( remove_keys(model_dict(template_layer), ['db_entity_key']), ) )[0]
def update_or_create_attribute_group(attribute_group): """ Update or create a behavior based on the given behavior """ return AttributeGroup.objects.update_or_create(key=attribute_group.key, defaults=model_dict( attribute_group, omit_fields=['key']))[0]
def update_layer_of_scenario(scenario): logger.info("Updating layer of db_entity_key %s, Scenario %s" % (db_entity_key, scenario.name)) db_entity_interest = DbEntityInterest.objects.get( config_entity=scenario, db_entity__key=db_entity_key) return Layer.objects.update_or_create( db_entity_interest=db_entity_interest, defaults=merge( remove_keys(model_dict(template_layer), ['db_entity_key']), ))[0]
def update_or_create_behavior(behavior): """ Update or create a behavior based on the given behavior """ updated_behavior = Behavior.objects.update_or_create( key=behavior.key, defaults=model_dict(behavior, omit_fields=['key', 'template_feature_behavior']) )[0] updated_behavior.update_or_create_associations(behavior) return behavior
def feature_behavior_from_behavior_template(self): """ The method to create a FeatureBehavior from the template_feature_behavior. This might be a subclass instance of FeatureBehavior and/or preconfigured values Override FeatureBehavior.set_defaults to set preconfigured values """ template_feature_behavior = self.subclassed_template_feature_behavior # Clone it, making a new intersection instance by removing the id # If the FeatureBehavior has no Intersection get it from the Behavior # Some behaviors, like background_imagery and result also have no Intersection intersection = template_feature_behavior.intersection_subclassed or self.intersection_subclassed if not intersection: raise Exception("No intersection for the template FeatureBehavior resolved") intersection.id = 0 logger.debug("Intersection from template is %s of subclass %s" % (model_dict(intersection, include_primary_key=True) if intersection else 'None', intersection.__class__ if intersection else 'None')) feature_behavior = template_feature_behavior.__class__( **merge(model_dict(template_feature_behavior), dict(behavior=self, is_template=False, intersection=intersection)) ) # Set any defaults defined by the base or subclass feature_behavior.set_defaults() return feature_behavior
def update_or_create_template_feature_behavior(self, template_feature_behavior): """ Saves an unsaved template FeatureBehavior, marking it as a template """ if template_feature_behavior and template_feature_behavior.id: return template_feature_behavior from footprint.main.models.geospatial.feature_behavior import FeatureBehavior from footprint.main.models.geospatial.db_entity import DbEntity return (template_feature_behavior.__class__ if template_feature_behavior else FeatureBehavior).objects.update_or_create( behavior=self, db_entity=DbEntity.objects.get(key='template_feature_behavior'), # Special DbEntity is_template=True, defaults=model_dict(template_feature_behavior) if template_feature_behavior else {} )[0]
def resolve_layer_configuration(config_entity, db_entity_key, layer): """ Creates a LayerConfiguration for imported layers using the template LayerConfigurations designed in the LayerConfigurationFixture.import_layer_configurations """ from footprint.client.configuration.fixture import LayerConfigurationFixture from footprint.client.configuration.utils import resolve_fixture # if a layer exists and it has a layer configuration update it if layer and layer.configuration: layer_configuration = LayerConfiguration(**merge( dict( db_entity_key=layer.db_entity_key, layer_library_key=layer.key, visible=layer.visible, # Convert the LayerStyle to a dict, which is what a LayerConfiguration expects, and wipe out the ids layer_style=model_dict(layer.medium)), dict( built_form_set_key=layer.configuration. get('built_form_key', None), sort_priority=layer.configuration.get('sort_priority', None), attribute_sort_priority=layer.configuration. get('attribute_sort_priority', None), column_alias_lookup=layer.configuration. get('column_alias_lookup', None), ) if layer.configuration else dict())) # if the the layer configuration doesn't exist, utilize the default layer configuration else: client_layer_configuration = resolve_fixture( "presentation", "layer", LayerConfigurationFixture, config_entity.schema(), config_entity=config_entity) geometry_type = config_entity.db_entity_by_key( db_entity_key).geometry_type layer_configuration = copy.copy( list( client_layer_configuration.import_layer_configurations( geometry_type))[0]) # Update the template LayerConfiguration to our db_entity_key layer_configuration.db_entity_key = db_entity_key return layer_configuration
def resolve_layer_configuration(config_entity, db_entity_key, layer): """ Creates a LayerConfiguration for imported layers using the template LayerConfigurations designed in the LayerConfigurationFixture.import_layer_configurations """ from footprint.client.configuration.fixture import LayerConfigurationFixture from footprint.client.configuration.utils import resolve_fixture # if a layer exists and it has a layer configuration update it if layer and layer.configuration: layer_configuration = LayerConfiguration( **merge( dict( db_entity_key=layer.db_entity_key, layer_library_key=layer.key, visible=layer.visible, # Convert the LayerStyle to a dict, which is what a LayerConfiguration expects, and wipe out the ids layer_style=model_dict(layer.medium) ), dict( built_form_set_key=layer.configuration.get('built_form_key', None), sort_priority=layer.configuration.get('sort_priority', None), attribute_sort_priority=layer.configuration.get('attribute_sort_priority', None), column_alias_lookup=layer.configuration.get('column_alias_lookup', None), ) if layer.configuration else dict() ) ) # if the the layer configuration doesn't exist, utilize the default layer configuration else: client_layer_configuration = resolve_fixture( "presentation", "layer", LayerConfigurationFixture, config_entity.schema(), config_entity=config_entity) geometry_type = config_entity.db_entity_by_key(db_entity_key).geometry_type layer_configuration = copy.copy(list(client_layer_configuration.import_layer_configurations(geometry_type))[0]) # Update the template LayerConfiguration to our db_entity_key layer_configuration.db_entity_key = db_entity_key return layer_configuration
def update_or_create_db_entity_and_interest(config_entity, config_db_entity): """ Sync a single db_entity_configuration or db_entity and its db_entity_interest :return A tuple of the DbEntityInterest and the created flag """ unique_key_combo = ['key', 'schema'] db_entity, created, updated = DbEntity.objects.update_or_create( # key and schema uniquely identify the DbEntity key=config_db_entity.key, schema=config_db_entity.schema, defaults=remove_keys(model_dict(config_db_entity), unique_key_combo)) db_entity.feature_behavior = config_db_entity.feature_behavior db_entity.save() logger.info("ConfigEntity/DbEntity Publishing. DbEntity: %s" % db_entity.full_name) # Create the DbEntityInterest through class instance which associates the ConfigEntity instance # to the DbEntity instance. For now the interest attribute is hard-coded to OWNER. This might # be used in the future to indicate other levels of interest interest = Interest.objects.get(key=Keys.INTEREST_OWNER) db_entity_interest, created, updated = DbEntityInterest.objects.update_or_create( config_entity=config_entity, db_entity=db_entity, interest=interest) #update the geography scope after the db_entity_interest saves as this is required to find 'owned' db_entites in a config entity if not db_entity.no_feature_class_configuration: feature_class_creator = FeatureClassCreator(config_entity, db_entity, no_ensure=True) db_entity.feature_class_configuration.geography_scope = config_entity.id if db_entity.feature_class_configuration.primary_geography \ else feature_class_creator.resolved_geography_scope.id db_entity.save() return db_entity_interest, created
raise Exception("Attempt to change locked behavior on DbEntity %s from Behavior %s to Behavior %s" % (self.full_name, self.feature_behavior.behavior.name, behavior.name)) existing_feature_behavior.delete() existing_feature_behavior = None existing_intersection = None if existing_feature_behavior: # If we still have a valid existing feature_behavior have it conform to anything about the Behavior # configuration that might have changed. updated_existing_feature_behavior = existing_feature_behavior template_feature_behavior = behavior.feature_behavior_from_behavior_template() existing_intersection = existing_feature_behavior.intersection_subclassed # If cloning set the id to None if existing_intersection and not updated_existing_feature_behavior.id: existing_intersection.id = None logger.debug("Existing Intersection: %s" % model_dict(existing_intersection, include_primary_key=True)) # TODO this seems problematic. The existing FeatureBehavior's tags should take precedence over the # Behavior's unless the former has no tags updated_existing_feature_behavior._tags = template_feature_behavior._tags else: # Get a new instance from the Behavior updated_existing_feature_behavior = behavior.feature_behavior_from_behavior_template() updated_existing_feature_behavior.set_defaults() # Intersection properties are defined on the Behavior and possibly extended or overridden on the FeatureBehavior # Every FeatureBehavior has its own Intersection instance so that we can customize the intersection for # the DbEntity. We always remove the is_template property that might have come the Behavior's Intersection intersection_dict = remove_keys( merge( model_dict(updated_existing_feature_behavior.intersection_subclassed), model_dict(configured_feature_behavior.intersection_subclassed),
def clone_or_update_db_entity_and_interest(config_entity, source_db_entity, db_entity, existing_db_entity_interest=None, override_on_update=False): """ Clones or updates the source_db_entity modified with the given kwargs (including possibly the key) into this ConfigEntity. This is used for a duplicate clone from one ConfigEntity (same DbEntity key) to another and also for creating a modified DbEntity for a Result from a non-Result DbEntity (different DbEntity key). A third case for this method is cloning a DbEntity within a ConfigEntity, which is not yet implemented. If the kwargs['override_on_update'] is True, the kwargs should override the target DbEntity attribute values on update. This is useful for the Result clone case where we want to pick up updates to the source DbEntity. But in the straight clone case we want to make the target DbEntity independent of the source once it is created. Returns the DbEntityInterest :param: config_entity. The config_entity of the DbEntities :param: Optional source_db_entity. The source of the clone :param: db_entity. DbEntity of attributes matching the DbEntity that need to override those of the source_db_entity. This might be the key, schema, etc. Also for db_entity.feature_class_owner, default None, is passed in for Results that are copying a reference DbEntity's feature_class_configuration with the db_entity key of the onwer. We don't want the result version of the feature_class_configuration to be used to create the feature_class tables, because that would make foreign key references point to the wrong feature class parents (which are named by the DbEntity id). phwewww! :param: existing_db_entity_interest: Optional DbEntityInterest if it already exists for the clone :param: override_on_update: Default False, optional and is described above. """ # Do a forced adoption of DbEntityInterests from the parent ConfigEntity. This makes sure that ConfigEntity has # the parent's DbEntityInterests before adding any of its own. Otherwise the parent's are never adopted and # are created from the db_entity_configurations instead, which is minimally less efficient # See _adopt_from_donor docs for an explanation. config_entity._adopt_from_donor('db_entities', True) # TODO Update to newer cleaner configuration style # key is always resolved by the db_entity or else the source DbEntity key key = db_entity.key or source_db_entity.key feature_behavior = db_entity._feature_behavior if existing_db_entity_interest: # Merge existing DbEntity data, including the id. Manually merge feature_behavior since it's not a real # property db_entity.__dict__.update( merge( model_dict(existing_db_entity_interest.db_entity, include_primary_key=True), dict(_feature_behavior=existing_db_entity_interest.db_entity.feature_behavior) ) ) if not db_entity._feature_behavior: from footprint.main.publishing.config_entity_initialization import get_behavior # If we don't already have a feature_behavior get it from the source DbEntity but 0 the id to make a copy # We'll also 0 out the Intersection of the FeatureBehavior and pass it to the FeatureBehavior creator # If we don't have a source_db_entity then create a default FeatureBehavior feature_behavior = source_db_entity.feature_behavior if \ source_db_entity else \ FeatureBehavior( behavior=get_behavior('reference') ) feature_behavior.id = None # Prefer the db_entity values over those of the source_db_entity db_entity.__dict__.update(merge( # Start with the source attributes model_dict(source_db_entity) if source_db_entity else {}, # Copy the feature_behavior by assigning it to the temp property _feature_behavior # Prefer the FeatureBehavior of the new DbEntity (for the Result case) dict(_feature_behavior=feature_behavior), # Override with the initial clone overrides or existing clone values. model_dict(db_entity), # If update_on_override then overwrite the clone with the source values # This is for the case of updating the source configuration and wanting to mirror changes to the Result DbEntities remove_keys(model_dict(source_db_entity), ['key', 'schema', 'name']) if source_db_entity and override_on_update else {}, # Set the clone source key if different than the target key dict(source_db_entity_key=source_db_entity.key) if source_db_entity and source_db_entity.key != key else {}, dict( # Override feature_class_configuration keys if specified in the kwargs and create_or_update_on_override is True feature_class_configuration=FeatureClassConfiguration(**merge( # start with the source's attributes source_db_entity.feature_class_configuration.__dict__ if source_db_entity else {}, # override with all those present on the clone if create_or_update_on_override is false, if true add only those unspecified on the source # The only exception is abstract_class_name, which must always come from the source remove_keys(db_entity.feature_class_configuration.__dict__, source_db_entity.feature_class_configuration.__dict__.keys() if \ source_db_entity and override_on_update else \ ['abstract_class_name'] ), # feature_class_owner is set to the owning db_entity key for a Result DbEntity creation so that # the result feature_class_configuration doesn't create feature classes/tables # We either copy this value from the source feature_class_configuration (clone case) or get it from the kwargs (create Result DbEntity case) dict( feature_class_owner=source_db_entity.feature_class_configuration.feature_class_owner if \ source_db_entity else \ db_entity.feature_class_configuration.feature_class_owner, ) )) ) )) # There must be a creator of a new DbEntity. The updater is that of the source DbEntity or simply the creator try: db_entity.creator = db_entity.creator except: db_entity.creator = source_db_entity.creator logger.debug(db_entity.creator.__class__) try: db_entity.updater = db_entity.updater except: db_entity.updater = db_entity.creator # Persist the feature_behavior FeatureBehavior._no_post_save_publishing = True db_entity.update_or_create_feature_behavior(db_entity._feature_behavior) # # save to persist the db_entity's knowledge of its behavior # db_entity.save() FeatureBehavior._no_post_save_publishing = False # This will trigger DbEntity post-save publishing, but the only thing that actually # runs is UserPublishing in order to give users permission to the DbEntity # The other publishers detect that the DbEntity has a source_db_entity_key and quit db_entity_interest = update_or_create_db_entity_and_interest(config_entity, db_entity)[0] # Copy the categories from the source if it exists if source_db_entity: db_entity_interest.db_entity.categories.add(*source_db_entity.categories.all()) return db_entity_interest
existing_feature_behavior = None existing_intersection = None if existing_feature_behavior: # If we still have a valid existing feature_behavior have it conform to anything about the Behavior # configuration that might have changed. updated_existing_feature_behavior = existing_feature_behavior template_feature_behavior = behavior.feature_behavior_from_behavior_template( ) existing_intersection = existing_feature_behavior.intersection_subclassed # If cloning set the id to None if existing_intersection and not updated_existing_feature_behavior.id: existing_intersection.id = None logger.debug( "Existing Intersection: %s" % model_dict(existing_intersection, include_primary_key=True)) # TODO this seems problematic. The existing FeatureBehavior's tags should take precedence over the # Behavior's unless the former has no tags updated_existing_feature_behavior._tags = template_feature_behavior._tags else: # Get a new instance from the Behavior updated_existing_feature_behavior = behavior.feature_behavior_from_behavior_template( ) updated_existing_feature_behavior.set_defaults() # Intersection properties are defined on the Behavior and possibly extended or overridden on the FeatureBehavior # Every FeatureBehavior has its own Intersection instance so that we can customize the intersection for # the DbEntity. We always remove the is_template property that might have come the Behavior's Intersection intersection_dict = remove_keys( merge( model_dict(
def clone_or_update_db_entity_and_interest(config_entity, source_db_entity, db_entity, existing_db_entity_interest=None, override_on_update=False): """ Clones or updates the source_db_entity modified with the given kwargs (including possibly the key) into this ConfigEntity. This is used for a duplicate clone from one ConfigEntity (same DbEntity key) to another and also for creating a modified DbEntity for a Result from a non-Result DbEntity (different DbEntity key). A third case for this method is cloning a DbEntity within a ConfigEntity, which is not yet implemented. If the kwargs['override_on_update'] is True, the kwargs should override the target DbEntity attribute values on update. This is useful for the Result clone case where we want to pick up updates to the source DbEntity. But in the straight clone case we want to make the target DbEntity independent of the source once it is created. Returns the DbEntityInterest :param: config_entity. The config_entity of the DbEntities :param: Optional source_db_entity. The source of the clone :param: db_entity. DbEntity of attributes matching the DbEntity that need to override those of the source_db_entity. This might be the key, schema, etc. Also for db_entity.feature_class_owner, default None, is passed in for Results that are copying a reference DbEntity's feature_class_configuration with the db_entity key of the onwer. We don't want the result version of the feature_class_configuration to be used to create the feature_class tables, because that would make foreign key references point to the wrong feature class parents (which are named by the DbEntity id). phwewww! :param: existing_db_entity_interest: Optional DbEntityInterest if it already exists for the clone :param: override_on_update: Default False, optional and is described above. """ # Do a forced adoption of DbEntityInterests from the parent ConfigEntity. This makes sure that ConfigEntity has # the parent's DbEntityInterests before adding any of its own. Otherwise the parent's are never adopted and # are created from the db_entity_configurations instead, which is minimally less efficient # See _adopt_from_donor docs for an explanation. config_entity._adopt_from_donor('db_entities', True) # TODO Update to newer cleaner configuration style # key is always resolved by the db_entity or else the source DbEntity key key = db_entity.key or source_db_entity.key feature_behavior = db_entity._feature_behavior if existing_db_entity_interest: # Merge existing DbEntity data, including the id. Manually merge feature_behavior since it's not a real # property db_entity.__dict__.update( merge( model_dict(existing_db_entity_interest.db_entity, include_primary_key=True), dict(_feature_behavior=existing_db_entity_interest.db_entity. feature_behavior))) if not db_entity._feature_behavior: from footprint.main.publishing.config_entity_initialization import get_behavior # If we don't already have a feature_behavior get it from the source DbEntity but 0 the id to make a copy # We'll also 0 out the Intersection of the FeatureBehavior and pass it to the FeatureBehavior creator # If we don't have a source_db_entity then create a default FeatureBehavior feature_behavior = source_db_entity.feature_behavior if \ source_db_entity else \ FeatureBehavior( behavior=get_behavior('reference') ) feature_behavior.id = None # Prefer the db_entity values over those of the source_db_entity db_entity.__dict__.update(merge( # Start with the source attributes model_dict(source_db_entity) if source_db_entity else {}, # Copy the feature_behavior by assigning it to the temp property _feature_behavior # Prefer the FeatureBehavior of the new DbEntity (for the Result case) dict(_feature_behavior=feature_behavior), # Override with the initial clone overrides or existing clone values. model_dict(db_entity), # If update_on_override then overwrite the clone with the source values # This is for the case of updating the source configuration and wanting to mirror changes to the Result DbEntities remove_keys(model_dict(source_db_entity), ['key', 'schema', 'name']) if source_db_entity and override_on_update else {}, # Set the clone source key if different than the target key dict(source_db_entity_key=source_db_entity.key) if source_db_entity and source_db_entity.key != key else {}, dict( # Override feature_class_configuration keys if specified in the kwargs and create_or_update_on_override is True feature_class_configuration=FeatureClassConfiguration(**merge( # start with the source's attributes source_db_entity.feature_class_configuration.__dict__ if source_db_entity else {}, # override with all those present on the clone if create_or_update_on_override is false, if true add only those unspecified on the source # The only exception is abstract_class_name, which must always come from the source remove_keys(db_entity.feature_class_configuration.__dict__, source_db_entity.feature_class_configuration.__dict__.keys() if \ source_db_entity and override_on_update else \ ['abstract_class_name'] ), # feature_class_owner is set to the owning db_entity key for a Result DbEntity creation so that # the result feature_class_configuration doesn't create feature classes/tables # We either copy this value from the source feature_class_configuration (clone case) or get it from the kwargs (create Result DbEntity case) dict( feature_class_owner=source_db_entity.feature_class_configuration.feature_class_owner if \ source_db_entity else \ db_entity.feature_class_configuration.feature_class_owner, ) )) ) )) # There must be a creator of a new DbEntity. The updater is that of the source DbEntity or simply the creator try: db_entity.creator = db_entity.creator except: db_entity.creator = source_db_entity.creator logger.debug(db_entity.creator.__class__) try: db_entity.updater = db_entity.updater except: db_entity.updater = db_entity.creator # Persist the feature_behavior FeatureBehavior._no_post_save_publishing = True db_entity.update_or_create_feature_behavior(db_entity._feature_behavior) # # save to persist the db_entity's knowledge of its behavior # db_entity.save() FeatureBehavior._no_post_save_publishing = False # This will trigger DbEntity post-save publishing, but the only thing that actually # runs is UserPublishing in order to give users permission to the DbEntity # The other publishers detect that the DbEntity has a source_db_entity_key and quit db_entity_interest = update_or_create_db_entity_and_interest( config_entity, db_entity)[0] # Copy the categories from the source if it exists if source_db_entity: db_entity_interest.db_entity.categories.add( *source_db_entity.categories.all()) return db_entity_interest