Beispiel #1
0
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)
Beispiel #2
0
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)
Beispiel #3
0
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
Beispiel #4
0
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