def source_model_post_save(sender, instance, created, **kwargs): # for clarity source_model = sender source_instance = instance affected_targets = source_instance._denorm_affected_targets if not affected_targets: # nothing to denorm return # # create a task for each affected target to update its instances # for target_model, affected_target in affected_targets.iteritems(): # if storage is shared_dict, then task will pluralize related_field_name to get target model's list field related_field_name = affected_target['related'] strategy = affected_target['strategy'] storage = affected_target['storage'] shards = affected_target['shards'] affected_fields = affected_target['fields'] #logging.info('affected target %s.%s for source %s: %s' % (target_model, related_field_name, source_model, affected_fields)) # for each affected target, create a separate task instance_id = source_instance.id tag = 'DENORM_SOURCE_%s_%s_TARGET_%s' % (util.get_model_name( source_model), instance_id, util.get_model_name(target_model)) payload = { 'created': timezone.now().isoformat(), 'strategy': strategy, 'storage': storage, 'instance_id': instance_id, 'source_model': util.get_model_name(source_model), 'target_model': util.get_model_name(target_model), 'related_field': related_field_name, 'fields': affected_fields, # TODO: queue name should be configurable 'queue_name': 'denorm' } if strategy == 'mapreduce': payload['shards'] = handler_for_name(shards)( source_instance) if shards else DEFAULT_MAP_REDUCE_SHARDS payload_string = util.dump_json(payload) logging.info( '[denorm source_model_post_save] queue task payload = %s' % payload_string) # create a pull task per target taskqueue.Queue('pull-denorm').add( taskqueue.Task(payload=payload_string, tag=tag, method='PULL')) # create ** one ** Task model instance used to track denorm tasks per source, particularly for throttling models.get_task_model().objects.create( source_model=util.get_model_name(source_model), source_instance_id=source_instance.id, user=source_instance._denorm_user, label=source_instance._denorm_label) # re-run post_init to reset _denorm_orig_values in case this instance gets saved again source_model_post_init(source_model, source_instance)
def source_model_post_save(sender, instance, created, **kwargs): # for clarity source_model = sender source_instance = instance affected_targets = source_instance._denorm_affected_targets if not affected_targets: # nothing to denorm return # # create a task for each affected target to update its instances # for target_model, affected_target in affected_targets.iteritems(): # if storage is shared_dict, then task will pluralize related_field_name to get target model's list field related_field_name = affected_target['related'] strategy = affected_target['strategy'] storage = affected_target['storage'] shards = affected_target['shards'] affected_fields = affected_target['fields'] #logging.info('affected target %s.%s for source %s: %s' % (target_model, related_field_name, source_model, affected_fields)) # for each affected target, create a separate task instance_id = source_instance.id tag = 'DENORM_SOURCE_%s_%s_TARGET_%s' % (util.get_model_name(source_model), instance_id, util.get_model_name(target_model)) payload = { 'created': timezone.now().isoformat(), 'strategy': strategy, 'storage': storage, 'instance_id': instance_id, 'source_model': util.get_model_name(source_model), 'target_model': util.get_model_name(target_model), 'related_field': related_field_name, 'fields': affected_fields, # TODO: queue name should be configurable 'queue_name': 'denorm' } if strategy == 'mapreduce': payload['shards'] = handler_for_name(shards)(source_instance) if shards else DEFAULT_MAP_REDUCE_SHARDS payload_string = util.dump_json(payload) logging.info('[denorm source_model_post_save] queue task payload = %s' % payload_string) # create a pull task per target taskqueue.Queue('pull-denorm').add( taskqueue.Task(payload=payload_string, tag=tag, method='PULL') ) # create ** one ** Task model instance used to track denorm tasks per source, particularly for throttling models.get_task_model().objects.create( source_model=util.get_model_name(source_model), source_instance_id=source_instance.id, user=source_instance._denorm_user, label=source_instance._denorm_label ) # re-run post_init to reset _denorm_orig_values in case this instance gets saved again source_model_post_init(source_model, source_instance)
def source_model_pre_save(sender, instance, raw, using, update_fields, **kwargs): # for clarity source_model = sender source_instance = instance created = not source_instance.id source_instance._denorm_affected_targets = affected_targets = {} # newly created instances will not need denormalization if created: return # denorm turned off if not getattr(source_instance, '_denorm', True): return source_graph = core.SOURCE_GRAPH[source_model] # # iterate through all fields to build up set of distinct affected targets that post_save signal receiver will process. # source_graph_fields = source_graph['fields'] orig_values = source_instance._denorm_orig_values for source_field, targets in source_graph_fields.iteritems(): old_value = orig_values[source_field] new_value = getattr(source_instance, source_field) if old_value != new_value: #logging.info('[%s] %s value changed from "%s" to "%s"' % (source_model, source_field, old_value, new_value)) for target in targets: target_model = target['target_model'] related_field_name = target['source'] storage = target['storage'] affected_targets[ target_model] = affected_target = affected_targets.get( target_model, { 'related': related_field_name, 'strategy': target['strategy'], 'storage': storage, 'shards': target['shards'], 'fields': {} }) # when task will update target, if storage is scalar, then field name is simply target model field name. # and if storage is shared_dict, then the field name is the dictionary key of the target model's denorm_data field. affected_fields_for_target = affected_target['fields'] affected_fields_for_target['%s_%s' % (related_field_name, source_field)] = new_value if not affected_targets: return # # check that denorm throttling threshold is not exceeded # # get user from thread-local variable set by middleware user = middleware.get_current_user() if not user or not isinstance(user, get_user_model( )) or not user.is_authenticated() or user.is_superuser: user = None source_instance._denorm_user = user # get denorm label used for throttling if 'label' in source_graph: # custom label set by application label = source_graph['label'](source_instance, user) else: # default label if user: label = '%s_%s' % (util.get_model_name(source_model), str(user.id)) else: # no label label = None source_instance._denorm_label = label throttles = source_graph.get('throttles') if not label or not throttles: # no throttling return # now validate each throttle # FIXME: we need to figure out if there is already a denorm task scheduled, and if so, then don't penalize throttle. # FIXME: perhaps we can use a Task.status field in combination with filter for source instance id. now = timezone.now() for throttle in throttles: num_requests, duration = util.parse_rate(throttle) # FIXME: this is a naive, inefficient implementation. we should cache task counts. if models.get_task_model().objects.filter( label=label, created__gt=now - timedelta(seconds=duration)).count() >= num_requests: raise exceptions.DenormThrottled
def source_model_pre_save(sender, instance, raw, using, update_fields, **kwargs): # for clarity source_model = sender source_instance = instance created = not source_instance.id source_instance._denorm_affected_targets = affected_targets = {} # newly created instances will not need denormalization if created: return # denorm turned off if not getattr(source_instance, '_denorm', True): return source_graph = core.SOURCE_GRAPH[source_model] # # iterate through all fields to build up set of distinct affected targets that post_save signal receiver will process. # source_graph_fields = source_graph['fields'] orig_values = source_instance._denorm_orig_values for source_field, targets in source_graph_fields.iteritems(): old_value = orig_values[source_field] new_value = getattr(source_instance, source_field) if old_value != new_value: #logging.info('[%s] %s value changed from "%s" to "%s"' % (source_model, source_field, old_value, new_value)) for target in targets: target_model = target['target_model'] related_field_name = target['source'] storage = target['storage'] affected_targets[target_model] = affected_target = affected_targets.get(target_model, { 'related': related_field_name, 'strategy': target['strategy'], 'storage': storage, 'shards': target['shards'], 'fields': {} }) # when task will update target, if storage is scalar, then field name is simply target model field name. # and if storage is shared_dict, then the field name is the dictionary key of the target model's denorm_data field. affected_fields_for_target = affected_target['fields'] affected_fields_for_target['%s_%s' % (related_field_name, source_field)] = new_value if not affected_targets: return # # check that denorm throttling threshold is not exceeded # # get user from thread-local variable set by middleware user = middleware.get_current_user() if not user or not isinstance(user, get_user_model()) or not user.is_authenticated() or user.is_superuser: user = None source_instance._denorm_user = user # get denorm label used for throttling if 'label' in source_graph: # custom label set by application label = source_graph['label'](source_instance, user) else: # default label if user: label = '%s_%s' % (util.get_model_name(source_model), str(user.id)) else: # no label label = None source_instance._denorm_label = label throttles = source_graph.get('throttles') if not label or not throttles: # no throttling return # now validate each throttle # FIXME: we need to figure out if there is already a denorm task scheduled, and if so, then don't penalize throttle. # FIXME: perhaps we can use a Task.status field in combination with filter for source instance id. now = timezone.now() for throttle in throttles: num_requests, duration = util.parse_rate(throttle) # FIXME: this is a naive, inefficient implementation. we should cache task counts. if models.get_task_model().objects.filter(label=label, created__gt=now - timedelta(seconds=duration)).count() >= num_requests: raise exceptions.DenormThrottled