def sync_entity(model_obj, is_deleted, entity_cache=None): """ Given a model object, either delete it from the entity table (if is_deleted=False) or update its entity values and relationships. """ entity_type = ContentType.objects.get_for_model(model_obj) if is_deleted: # Delete the entity, taking in the case of where the entity may not exist # before hand Entity.objects.filter(entity_type=entity_type, entity_id=model_obj.id).delete() else: entity_cache = entity_cache if entity_cache is not None else {} if not entity_cache.get((entity_type, model_obj.id)): # Create or update the entity entity = Entity.objects.upsert( entity_type=entity_type, entity_id=model_obj.id, updates={ 'entity_meta': model_obj.get_entity_meta(), 'is_active': model_obj.is_entity_active(), })[0] # Delete all of the existing relationships super to this entity sync(entity.super_relationships.all(), [ EntityRelationship( super_entity=sync_entity(super_model_obj, False, entity_cache), sub_entity=entity, ) for super_model_obj in model_obj.get_super_entities() ], ['super_entity_id', 'sub_entity_id']) entity_cache[(entity_type, model_obj.id)] = entity return entity_cache[(entity_type, model_obj.id)]
def save(self, *args, **kwargs): """ Builds the objects managed by the template before saving the template. """ super(SmartManager, self).save(*args, **kwargs) smart_manager = import_string(self.smart_manager_class)(self.template) primary_built_obj = smart_manager.build() # Do an update of the primary object type and id after it has been built. We use an update since # you can't call save in a save method. We may want to put this in post_save as well later. if primary_built_obj: self.primary_obj_type = ContentType.objects.get_for_model(primary_built_obj) self.primary_obj_id = primary_built_obj.id SmartManager.objects.filter(id=self.id).update( primary_obj_type=self.primary_obj_type, primary_obj_id=primary_built_obj.id) # Sync all of the objects from the built template sync(self.smartmanagerobject_set.all(), [ SmartManagerObject( smart_manager=self, model_obj_id=built_obj.id, model_obj_type=ContentType.objects.get_for_model(built_obj, for_concrete_model=False), ) for built_obj in smart_manager.built_objs ], ['smart_manager_id', 'model_obj_id', 'model_obj_type_id'])
def save(self, *args, **kwargs): """ Builds the objects managed by the template before saving the template. """ super(SmartManager, self).save(*args, **kwargs) smart_manager = import_string(self.smart_manager_class)(self.template) primary_built_obj = smart_manager.build() # Do an update of the primary object type and id after it has been built. We use an update since # you can't call save in a save method. We may want to put this in post_save as well later. if primary_built_obj: self.primary_obj_type = ContentType.objects.get_for_model( primary_built_obj) self.primary_obj_id = primary_built_obj.id SmartManager.objects.filter(id=self.id).update( primary_obj_type=self.primary_obj_type, primary_obj_id=primary_built_obj.id) # Sync all of the objects from the built template sync(self.smartmanagerobject_set.all(), [ SmartManagerObject( smart_manager=self, model_obj_id=built_obj.id, model_obj_type=ContentType.objects.get_for_model( built_obj, for_concrete_model=False), ) for built_obj in smart_manager.built_objs ], ['smart_manager_id', 'model_obj_id', 'model_obj_type_id'])
def sync_perm_sets(self, perm_sets): """ Syncs the provided dictionary of perm sets to PermSet models. """ sync(self.get_queryset(), [ PermSet(name=name, display_name=config.get('display_name', '')) for name, config in perm_sets.items() ], ['name'], ['display_name'])
def sync_perms(self, perms): """ Syncs the perms to Perm models. """ sync(self.get_queryset(), [ Perm(name=name, display_name=config.get('display_name', '')) for name, config in perms.items() ], ['name'], ['display_name'])
def sync_perm_sets(self, perm_sets): """ Syncs the provided dictionary of perm sets to PermSet models. """ sync( self.get_queryset(), [PermSet(name=name, display_name=config.get('display_name', '')) for name, config in perm_sets.items()], ['name'], ['display_name'])
def sync_perms(self, perms): """ Syncs the perms to Perm models. """ sync( self.get_queryset(), [Perm(name=name, display_name=config.get('display_name', '')) for name, config in perms.items()], ['name'], ['display_name'])
def _sync_entity_relationships(self): """ After all entities have been synced, the entity relationships of every synced entity is still stored in the _entity_relationships_to_sync variable. Sync these relationships. """ manager_utils.sync( EntityRelationship.objects.filter( sub_entity__in=self._entity_relationships_to_sync.keys()), chain(*self._entity_relationships_to_sync.values()), ['super_entity_id', 'sub_entity_id'])
def _sync_entity_relationships(self): """ After all entities have been synced, the entity relationships of every synced entity is still stored in the _entity_relationships_to_sync variable. Sync these relationships. """ manager_utils.sync( EntityRelationship.objects.filter(sub_entity__in=self._entity_relationships_to_sync.keys()), chain(*self._entity_relationships_to_sync.values()), ['super_entity_id', 'sub_entity_id'] )
def update(self, **updates): """ Takes a template of updates and updates the data schema. The following are possible keyword arguments. 'model_content_type': The string path of a model that this schema represents (or None), 'fieldschema_set': [{ 'field_key': The field key of the field, 'display_name': The display name for the field (or the 'field_key' by default), 'field_type': The FieldSchemaType type of the field, 'uniqueness_order': The order this field is in the uniquessness constraint (or None by default), 'field_position': The position of this field if it can be parsed by an array (or None by default), 'field_format': The format of this field (or None by default), 'default_value': The default value of this field (or None by default), 'fieldoption_set': The set of options for the field schema (optional), }, { Additional field schemas... }] """ if 'model_content_type' in updates: self.model_content_type = updates['model_content_type'] self.save() if 'fieldschema_set' in updates: # Sync the field schema models sync(self.fieldschema_set.all(), [ FieldSchema( data_schema=self, field_key=fs_values['field_key'], display_name=fs_values.get('display_name', ''), field_type=fs_values['field_type'], uniqueness_order=fs_values.get('uniqueness_order', None), field_position=fs_values.get('field_position', None), field_format=fs_values.get('field_format', None), default_value=fs_values.get('default_value', None), has_options='fieldoption_set' in fs_values and fs_values['fieldoption_set'], ) for fs_values in updates['fieldschema_set'] ], ['field_key'], [ 'display_name', 'field_key', 'field_type', 'uniqueness_order', 'field_position', 'field_format', 'default_value', 'has_options' ]) # Sync the options of the field schema models if they are present for fs_values in updates['fieldschema_set']: if 'fieldoption_set' in fs_values: fs = self.fieldschema_set.get(field_key=fs_values['field_key']) sync(fs.fieldoption_set.all(), [ FieldOption( field_schema=fs, value=f_option, ) for f_option in fs_values['fieldoption_set'] ], ['value'], ['value'])
def sync_perm_levels(self, perms): """ Given a dictionary of perms that map to perm levels, sync the perm levels to PermLevel objects in the database. """ perm_objs = {p.name: p for p in Perm.objects.all()} perm_levels = [] for perm, perm_config in perms.items(): assert(perm_config['levels']) for l, level_config in perm_config['levels'].items(): perm_levels.append( PermLevel(perm=perm_objs[perm], name=l, display_name=level_config.get('display_name', ''))) sync(self.get_queryset(), perm_levels, ['name', 'perm'], ['display_name'])
def save(self, *args, **kwargs): """ Builds the objects managed by the template before saving the template. """ super(SmartManager, self).save(*args, **kwargs) # Built the template smart_manager = import_by_path(self.smart_manager_class)(self.template) smart_manager.build() # Sync all of the objects from the built template sync(self.smartmanagerobject_set.all(), [ SmartManagerObject( smart_manager=self, model_obj_id=built_obj.id, model_obj_type=ContentType.objects.get_for_model(built_obj, for_concrete_model=False), ) for built_obj in smart_manager.built_objs ], ['smart_manager_id', 'model_obj_id', 'model_obj_type_id'])
def upsert_entity_relationships(self, queryset, entity_relationships): """ Upsert entity relationships to the database :param queryset: The base queryset to use :param entity_relationships: The entity relationships to ensure exist in the database """ # Select the relationships for update if entity_relationships: list(queryset.select_for_update().values_list( 'id', flat=True )) # Sync the relationships return manager_utils.sync( queryset=queryset, model_objs=entity_relationships, unique_fields=['sub_entity_id', 'super_entity_id'], update_fields=[], return_upserts=True )
def upsert_entities(self, entities, sync=False): """ Upsert a list of entities to the database :param entities: The entities to sync :param sync: Do a sync instead of an upsert """ # Select the entities we are upserting for update to reduce deadlocks if entities: # Default select for update query when syncing all select_for_update_query = ( 'SELECT FROM {table_name} FOR NO KEY UPDATE' ).format( table_name=Entity._meta.db_table ) select_for_update_query_params = [] # If we are not syncing all, only select those we are updating if not sync: select_for_update_query = ( 'SELECT FROM {table_name} WHERE (entity_type_id, entity_id) IN %s FOR NO KEY UPDATE' ).format( table_name=Entity._meta.db_table ) select_for_update_query_params = [tuple( (entity.entity_type_id, entity.entity_id) for entity in entities )] # Select the items for update with connection.cursor() as cursor: cursor.execute(select_for_update_query, select_for_update_query_params) # Compute the initial queryset and the initial state of the entities we are syncing # We need the initial state so we can compare it to the new state to determine any # entities that were activated or deactivated initial_queryset = Entity.all_objects.all() if not sync: initial_queryset = Entity.all_objects.extra( where=['(entity_type_id, entity_id) IN %s'], params=[tuple( (entity.entity_type_id, entity.entity_id) for entity in entities )] ) initial_entity_activation_state = { entity[0]: entity[1] for entity in initial_queryset.values_list('id', 'is_active') } # Sync all the entities if the sync flag is passed if sync: upserted_entities = manager_utils.sync( queryset=initial_queryset, model_objs=entities, unique_fields=['entity_type_id', 'entity_id'], update_fields=['entity_kind_id', 'entity_meta', 'display_name', 'is_active'], return_upserts=True ) # Otherwise we want to upsert our entities else: upserted_entities = manager_utils.bulk_upsert( queryset=initial_queryset, model_objs=entities, unique_fields=['entity_type_id', 'entity_id'], update_fields=['entity_kind_id', 'entity_meta', 'display_name', 'is_active'], return_upserts=True ) # Compute the current state of the entities current_entity_activation_state = { entity.id: entity.is_active for entity in upserted_entities } # Computed the changed activation state of the entities changed_entity_activation_state = {} all_entity_ids = set(initial_entity_activation_state.keys()) | set(current_entity_activation_state.keys()) for entity_id in all_entity_ids: # Get the initial activation state of the entity # Default to false so we only detect when the model has actually changed initial_activation_state = initial_entity_activation_state.get(entity_id, False) # Get the current state of the entity # Default to false here since the upserts do not return is_active=False due # to the default object manager excluding these current_activation_state = current_entity_activation_state.get(entity_id, False) # Check if the state changed and at it to the changed entity state if initial_activation_state != current_activation_state: changed_entity_activation_state[entity_id] = current_activation_state # Return the upserted entities return upserted_entities, changed_entity_activation_state