Пример #1
0
def m2m_changed_handler(sender, *args, **kwargs):
    """
    A model's save() never gets called on ManyToManyField changes, m2m_changed-signal is sent.
    sender = dynamically generated model in m2m-table
    instance = parent
    related_instance = instance being m2m'ed
    """
    action = kwargs['action']
    instance = kwargs['instance']
    logger.debug("m2m_changed: %s (%s) {%s}"%(sender, args, kwargs))

    bulk = []
    if is_df(instance) and (action in ['post_add', 'post_remove']):
        pk_set = list(kwargs.get('pk_set') or [])
        relation_name = sender._meta.db_table.replace(sender._meta.app_label + '_' + instance.__class__.__name__.lower() + '_', '')
        relations = {k.name:k for k in m2m_relations(instance)}
        field = relations[relation_name]
        for pk in pk_set:
            related_instance = get_relation(relations[relation_name]).objects.get(pk=pk)
            changes = {field.name: {'changed': [pk],
                                    'changed_to_string': six.text_type(related_instance)}}
            # reflect change
            bulk.append(History.objects.add(
                    action=action,
                    changes=changes,
                    model=instance,
                    commit=False,))
            # m2m to reflect on changes
            field = get_field(field)
            changes = {field.name: {'changed': [instance.pk],
                                    'changed_to_string': six.text_type(instance),
                                    'm2mpg': True,}}
            bulk.append(History.objects.add(
                    action='add' if action in ['post_add'] else 'rem',
                    changes=changes,
                    model=related_instance,
                    commit=False,))
    if is_df(instance) and (action in ['pre_clear']):
        # "For the pre_clear and post_clear actions, this is None."
        # TODO: should defer this until post_clear is done to be sure it happened
        # TODO: background job, optional execution
        relations = instance._meta.get_all_related_many_to_many_objects()
        for relation in relations:
            instances = get_m2m_reverse_instances(instance, relation)
            field = get_model_relation_by_instance(kwargs['model'], instance)
            changes = {field.name: {'changed': [instance.pk],
                                    'changed_to_string': six.text_type(instance)}}
            for k in instances:
                bulk.append(History.objects.add(
                        action=action,
                        changes=changes,
                        model=k,
                        commit=False,))

    if bulk:
        History.objects.bulk_create(bulk)
Пример #2
0
def handle_m2m(sender, *args, **kwargs):
    """
    A model's save() never gets called on ManyToManyField changes, m2m_changed-signal is sent.
    sender = dynamically generated model in m2m-table
    instance = parent
    related_instance = instance being m2m'ed
    """
    action = kwargs['action']
    instance = kwargs['instance']
    if hasattr(instance, 'get_changes') and (action == 'post_add' or action == 'post_remove'):
        pk_set = list(kwargs['pk_set'])
        relation_name = sender._meta.db_table.replace(sender._meta.app_label + '_' + instance.__class__.__name__.lower() + '_', '')
        for pk in pk_set:
            relations = {k.name:k for k in instance.get_m2m_relations()}
            related_instance = get_relation(relations[relation_name]).objects.get(pk=pk)
            field = relations[relation_name]
            changes = {field.name: {'changed': [pk], 'changed_to_string': six.text_type(related_instance)}}
            # TODO: add meta information about relation
            # - parent, child
            History.objects.add(
                    action=ACTION_MAP[action],
                    changes=changes,
                    model=instance,)
Пример #3
0
    def add(self, action, changes, model, user=None, object_id=None):
        if not getattr(settings, 'DJANGO_HISTORY_TRACK', True):
            return
        request = get_current_request()
        if not user:
            if request:
                user = request.user
        user_id = user.pk if user else None
        model_ct = ContentType.objects.get_for_model(model)
        model_id = model_ct.pk
        object_id = object_id or model.pk

        # exclusion / none -checks
        excludes = get_setting('EXCLUDE_CHANGES')
        if excludes:
            excludes_for_model = excludes.get("{0}.{1}".format(model_ct.app_label, model_ct.model))
            if excludes_for_model:
                for k,v in six.iteritems(copy.deepcopy(changes)):
                    if k in excludes_for_model.get('fields', []):
                        del changes[k]
        if not changes:
            return

        # for FKs, get old/new information
        fields = model._meta.local_fields
        def get_item(model, pk):
            value = None
            if isinstance(pk, models.Model):
                pk = copy.deepcopy(pk.pk)
            try:
                value = six.text_type(model.objects.get(pk=pk))
            except Exception as e:
                if settings.DEBUG: print(e)
            return value

        def match_field(model, changed_field):
            try:
                field = model._meta.get_field(k)
            except:
                field = model._meta.get_field(k.replace('_id', ''))
            return field

        for k,v in six.iteritems(changes):
            field = match_field(model, k)
            v['verbose_name'] = six.text_type(field.verbose_name)
            if isinstance(field, models.ForeignKey):
                parent_model = get_relation(field)
                if v['new']:
                    v['new_to_string'] = get_item(parent_model, v['new'])
                    if isinstance(v['new'], models.Model):
                        v['new'] = v['new'].pk
                if v['old']:
                    v['old_to_string'] = get_item(parent_model, v['old'])
                    if isinstance(v['old'], models.Model):
                        v['old'] = v['old'].pk
                v['is_fk'] = True
            if isinstance(field, models.ManyToManyField):
                v['is_m2m'] = True
                v['m2m_css_class'] = 'old_change'
                if 'm2m.add' in action:
                    v['m2m_css_class'] = 'new_change'
        if 'delete' in action:# M2M copied on delete
            for field in model._meta.local_many_to_many:
                pk_set = getattr(model, field.name).all()
                row = {
                    'changed': list([k.pk for k in pk_set]),
                    'is_m2m': True,
                    'm2m_css_class': 'old_change',
                    'changed_to_string': u", ".join([six.text_type(k) for k in pk_set]),
                    'verbose_name': six.text_type(field.verbose_name),
                }
                changes[field.name] = row
        changeset = {
        'fields': changes,
        'model': {
                'to_string': six.text_type(model),
                'verbose_name': six.text_type(model._meta.verbose_name),
                'content_type': {
                    'id': model_ct.pk,
                    'app_label': model_ct.app_label,
                    'model': model_ct.model,
                }
            },
        'user': {
                'to_string': six.text_type(user),
            }
        }

        history = self.model(
            action=action,
            changes=changeset,
            model=model_id,
            user=user_id,
            object_id=object_id,)
        history.save(force_insert=True)
Пример #4
0
def get_model_relation_by_instance(model, relation):
    return [k for k in m2m_relations(model) if \
            isinstance(relation, get_relation(k))].pop()