def get_current_request():
    return getattr(_thread_locals, 'request', None) def get_current_user():
    request = get_current_request()
    if request:
        return getattr(request, 'user', None) class CRUDEvent(models.Model):
    CREATE = 1
    UPDATE = 2
    DELETE = 3
    M2M_CHANGE = 4
    M2M_CHANGE_REV = 5
    M2M_ADD = 6
    M2M_ADD_REV = 7
    M2M_REMOVE = 8
    M2M_REMOVE_REV = 9
    M2M_CLEAR = 10
    M2M_CLEAR_REV = 11

    TYPES = (
        (CREATE, _('Create')),
        (UPDATE, _('Update')),
        (DELETE, _('Delete')),
        (M2M_CHANGE, _('Many-to-Many Change')),
        (M2M_CHANGE_REV, _('Reverse Many-to-Many Change')),
        (M2M_ADD, _('Many-to-Many Add')),
        (M2M_ADD_REV, _('Reverse Many-to-Many Add')),
        (M2M_REMOVE, _('Many-to-Many Remove')),
        (M2M_REMOVE_REV, _('Reverse Many-to-Many Remove')),
        (M2M_CLEAR, _('Many-to-Many Clear')),
        (M2M_CLEAR_REV, _('Reverse Many-to-Many Clear')),
    )

    event_type = models.SmallIntegerField(choices=TYPES, verbose_name=_('Event type'))
    object_id = models.CharField(max_length=255, verbose_name=_('Object ID'))
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, db_constraint=False, verbose_name=_('Content type'))
    object_repr = models.TextField(null=True, blank=True, verbose_name=_('Object representation'))
    object_json_repr = models.TextField(null=True, blank=True, verbose_name=_('Object JSON representation'))
    changed_fields = models.TextField(null=True, blank=True, verbose_name=_('Changed fields'))
    user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
                             blank=True, on_delete=models.SET_NULL,
                             db_constraint=False, verbose_name=_('User'))
    user_pk_as_string = models.CharField(max_length=255, null=True, blank=True,
                                         help_text=_('String version of the user pk'), verbose_name=_('User PK as string'))
    datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date time'))

    def is_create(self):
        return self.CREATE == self.event_type

    def is_update(self):
        return self.UPDATE == self.event_type

    def is_delete(self):
        return self.DELETE == self.event_type

    class Meta:
        verbose_name = _('CRUD event')
        verbose_name_plural = _('CRUD events')
        ordering = ['-datetime']
        index_together = ['object_id', 'content_type', ] REGISTERED_CLASSES = getattr(settings, 'DJANGO_EASY_AUDIT_REGISTERED_CLASSES', []) UNREGISTERED_CLASSES = [CRUDEvent, LoginEvent, RequestEvent, Migration, Session, Permission, ContentType, MigrationRecorder.Migration] WATCH_MODEL_EVENTS = getattr(settings, 'DJANGO_EASY_AUDIT_WATCH_MODEL_EVENTS', True) CRUD_DIFFERENCE_CALLBACKS = [] LOGGING_BACKEND = getattr(settings, 'DJANGO_EASY_AUDIT_LOGGING_BACKEND', 'easyaudit.backends.ModelBackend') DATABASE_ALIAS = getattr(settings, 'DJANGO_EASY_AUDIT_DATABASE_ALIAS', django.db.utils.DEFAULT_DB_ALIAS) def get_m2m_field_name(model, instance):
    """
    Finds M2M field name on instance
    Called from m2m_changed signal
    :param model: m2m_changed signal model.
    :type model: Model
    :param instance:m2m_changed signal instance.
    :type new: Model
    :return: ManyToManyField name of instance related to model.
    :rtype: str
    """
    for x in model._meta.related_objects:
        if x.related_model().__class__ == instance.__class__:
            return x.remote_field.name def model_delta(old_model, new_model):
    """
    Provides delta/difference between two models
    :param old: The old state of the model instance.
    :type old: Model
    :param new: The new state of the model instance.
    :type new: Model
    :return: A dictionary with the names of the changed fields as keys and a
             two tuple of the old and new field values
             as value.
    :rtype: dict
    """

    delta = {}
    fields = new_model._meta.fields
    for field in fields:
        old_value = get_field_value(old_model, field)
        new_value = get_field_value(new_model, field)
        if old_value != new_value:
            delta[field.name] = [smart_str(old_value),
                                 smart_str(new_value)]

    if len(delta) == 0:
        delta = None

    return delta
import json
import logging
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from django.core.serializers.json import DjangoJSONEncoder
from django.db import transaction
from django.db.models import signals
from django.utils import timezone
from django.utils.encoding import force_str
from django.utils.module_loading import import_string
from easyaudit.middleware.easyaudit import get_current_request, \
    get_current_user
from easyaudit.models import CRUDEvent
from easyaudit.settings import REGISTERED_CLASSES, UNREGISTERED_CLASSES, \
    WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS, LOGGING_BACKEND, \
    DATABASE_ALIAS
from easyaudit.utils import get_m2m_field_name, model_delta
        for registered_class in REGISTERED_CLASSES:
            if isinstance(instance, registered_class):
                break
        else:
            return False

    # all good
    return True


# signals
def pre_save(sender, instance, raw, using, update_fields, **kwargs):
    """https://docs.djangoproject.com/es/1.10/ref/signals/#post-save"""
    if raw:
        # Return if loading Fixtures
        return

    try:
        with transaction.atomic(using=using):
            if not should_audit(instance):
                return False
            try:
                object_json_repr = serializers.serialize("json", [instance])
            except Exception:
                # We need a better way for this to work. ManyToMany will fail on pre_save on create
                return None

            # Determine if the instance is a create
            created = instance.pk is None or instance._state.adding

            # created or updated?
            if not created:
                old_model = sender.objects.get(pk=instance.pk)
                delta = model_delta(old_model, instance)
                if not delta and getattr(settings, "DJANGO_EASY_AUDIT_CRUD_EVENT_NO_CHANGED_FIELDS_SKIP", False):
                    return False
                changed_fields = json.dumps(delta)
                event_type = CRUDEvent.UPDATE

            # user
            try:
                user = get_current_user()
                # validate that the user still exists
                user = get_user_model().objects.get(pk=user.pk)
            except:
                user = None

            if isinstance(user, AnonymousUser):
                user = None

            # callbacks
            kwargs['request'] = get_current_request()  # make request available for callbacks
            create_crud_event = all(
                callback(instance, object_json_repr, created, raw, using, update_fields, **kwargs)
                for callback in CRUD_DIFFERENCE_CALLBACKS if callable(callback))
            # create crud event only if all callbacks returned True
            if create_crud_event and not created:
                c_t = ContentType.objects.get_for_model(instance)

                def crud_flow():
                    try:
                        # atomicity based on the easyaudit database alias
                        with transaction.atomic(using=DATABASE_ALIAS):
                            crud_event = audit_logger.crud({
                                'event_type': event_type,
                                'object_repr': str(instance),
                                'object_json_repr': object_json_repr,
                                'changed_fields': changed_fields,
                                'content_type_id': c_t.id,
                                'object_id': instance.pk,
                                'user_id': getattr(user, 'id', None),
                                'datetime': timezone.now(),
                                'user_pk_as_string': str(user.pk) if user else user
                            })
                    except Exception as e:
                        try:
                            logger.exception(
                                "easy audit had a pre_save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
                                    instance, instance.pk))
                        except Exception:
                            pass
                if getattr(settings, "TEST", False):
                    crud_flow()
                else:
                    transaction.on_commit(crud_flow, using=using)
    except Exception:
        logger.exception('easy audit had a pre-save exception.')


def post_save(sender, instance, created, raw, using, update_fields, **kwargs):
    """https://docs.djangoproject.com/es/1.10/ref/signals/#post-save"""
    if raw:
        # Return if loading Fixtures
        return

    try:
        with transaction.atomic(using=using):
            if not should_audit(instance):
                return False
            object_json_repr = serializers.serialize("json", [instance])

            # created or updated?
            if created:
                event_type = CRUDEvent.CREATE

            # user
            try:
                user = get_current_user()
                # validate that the user still exists
                user = get_user_model().objects.get(pk=user.pk)
            except:
                user = None

            if isinstance(user, AnonymousUser):
                user = None

            # callbacks
            kwargs['request'] = get_current_request()  # make request available for callbacks
            create_crud_event = all(callback(instance, object_json_repr,
                                             created, raw, using, update_fields, **kwargs)

                                    for callback in CRUD_DIFFERENCE_CALLBACKS