def model_instance_diff(old, new): """ Calculates the differences between two model instances. One of the instances may be ``None`` (i.e., a newly created model or deleted model). This will cause all fields with a value to have changed (from ``None``). :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 """ from auditlog.registry import auditlog if not(old is None or isinstance(old, Model)): raise TypeError("The supplied old instance is not a valid model instance.") if not(new is None or isinstance(new, Model)): raise TypeError("The supplied new instance is not a valid model instance.") diff = {} if old is not None and new is not None: fields = set(old._meta.fields + new._meta.fields) model_fields = auditlog.get_model_fields(new._meta.model) elif old is not None: fields = set(get_fields_in_model(old)) model_fields = auditlog.get_model_fields(old._meta.model) elif new is not None: fields = set(get_fields_in_model(new)) model_fields = auditlog.get_model_fields(new._meta.model) else: fields = set() model_fields = None # Check if fields must be filtered if model_fields and (model_fields['include_fields'] or model_fields['exclude_fields']) and fields: filtered_fields = [] if model_fields['include_fields']: filtered_fields = [field for field in fields if field.name in model_fields['include_fields']] else: filtered_fields = fields if model_fields['exclude_fields']: filtered_fields = [field for field in filtered_fields if field.name not in model_fields['exclude_fields']] fields = filtered_fields for field in fields: old_value = get_field_value(old, field) new_value = get_field_value(new, field) if old_value != new_value: diff[field.name] = (smart_text(old_value), smart_text(new_value)) if len(diff) == 0: diff = None return diff
def log_create(self, instance, **kwargs): """ Helper method to create a new log entry. This method automatically populates some fields when no explicit value is given. :param instance: The model instance to log a change for. :type instance: Model :param kwargs: Field overrides for the :py:class:`LogEntry` object. :return: The new log entry or `None` if there were no changes. :rtype: LogEntry """ changes = kwargs.get('changes', None) pk = self._get_pk_value(instance) if changes is not None: content_type = ContentType.objects.get_for_model(instance) kwargs.setdefault('content_type', content_type) kwargs.setdefault('object_pk', pk) kwargs.setdefault('object_repr', smart_text(instance)) from auditlog.registry import auditlog model_fields = auditlog.get_model_fields(instance._meta.model) if model_fields['relate_field']: relate_object = getattr(instance, model_fields['relate_field']) kwargs.setdefault('relate_id', relate_object.pk) kwargs.setdefault( 'relate_content_type', ContentType.objects.get_for_model(relate_object)) print 'relate_field', getattr(instance, model_fields['relate_field']) else: kwargs.setdefault('relate_id', pk) kwargs.setdefault('relate_content_type', content_type) if isinstance(pk, integer_types): kwargs.setdefault('object_id', pk) get_additional_data = getattr(instance, 'get_additional_data', None) if callable(get_additional_data): kwargs.setdefault('additional_data', get_additional_data()) # Delete log entries with the same pk as a newly created model. This should only be necessary when an pk is # used twice. if kwargs.get('action', None) is LogEntry.Action.CREATE: if kwargs.get('object_id', None) is not None and self.filter( content_type=kwargs.get('content_type'), object_id=kwargs.get('object_id')).exists(): self.filter(content_type=kwargs.get('content_type'), object_id=kwargs.get('object_id')).delete() else: self.filter(content_type=kwargs.get('content_type'), object_pk=kwargs.get('object_pk', '')).delete() # save LogEntry to same database instance is using db = instance._state.db return self.create( **kwargs) if db is None or db == '' else self.using(db).create( **kwargs) return None
def changes_display_dict(self): """ :return: The changes recorded in this log entry intended for display to users as a dictionary object. """ # Get the model and model_fields from auditlog.registry import auditlog model = self.content_type.model_class() model_fields = auditlog.get_model_fields(model._meta.model) changes_display_dict = {} # grab the changes_dict and iterate through for field_name, values in iteritems(self.changes_dict): # try to get the field attribute on the model try: field = model._meta.get_field(field_name) except FieldDoesNotExist: changes_display_dict[field_name] = values continue values_display = [] # handle choices fields and Postgres ArrayField to get human readable version if field.choices or hasattr(field, 'base_field') and getattr(field.base_field, 'choices', False): choices_dict = dict(field.choices or field.base_field.choices) for value in values: try: value = ast.literal_eval(value) if type(value) is [].__class__: values_display.append(', '.join([choices_dict.get(val, 'None') for val in value])) else: values_display.append(choices_dict.get(value, 'None')) except ValueError: values_display.append(choices_dict.get(value, 'None')) except: values_display.append(choices_dict.get(value, 'None')) else: field_type = field.get_internal_type() for value in values: # handle case where field is a datetime, date, or time type if field_type in ["DateTimeField", "DateField", "TimeField"]: try: value = parser.parse(value) if field_type == "DateField": value = value.date() elif field_type == "TimeField": value = value.time() value = formats.localize(value) except ValueError: pass # check if length is longer than 140 and truncate with ellipsis if len(value) > 140: value = "{}...".format(value[:140]) values_display.append(value) verbose_name = model_fields['mapping_fields'].get(field.name, field.verbose_name) changes_display_dict[verbose_name] = values_display return changes_display_dict
def get_fields_in_model(instance: Any) -> List: """ Returns the list of fields in the given model instance. :param instance: The model instance to get the fields for :return: The list of fields for the given model (instance) """ from auditlog.registry import auditlog attrs = object_mapper(instance).iterate_properties model_attrs = auditlog.get_model_fields(instance.__class__) if model_attrs['include_fields']: attrs = (attr for attr in attrs if attr.key in model_attrs['include_fields']) if model_attrs['exclude_fields']: attrs = (attr for attr in attrs if attr.key not in model_attrs['exclude_fields']) return attrs
def changes_display_dict(self): """ :return: The changes recorded in this log entry intended for display to users as a dictionary object. """ # Get the model and model_fields from auditlog.registry import auditlog model = self.content_type.model_class() model_fields = auditlog.get_model_fields(model._meta.model) changes_display_dict = {} # grab the changes_dict and iterate through for field_name, values in iteritems(self.changes_dict): # try to get the field attribute on the model try: field = model._meta.get_field(field_name) except FieldDoesNotExist: changes_display_dict[field_name] = values continue values_display = [] # handle choices fields and Postgres ArrayField to get human readable version choices_dict = None if hasattr(field, 'choices') and len(field.choices) > 0: choices_dict = dict(field.choices) if hasattr(field, 'base_field') and getattr(field.base_field, 'choices', False): choices_dict = dict(field.base_field.choices) if choices_dict: for value in values: try: value = ast.literal_eval(value) if type(value) is [].__class__: values_display.append(', '.join([choices_dict.get(val, 'None') for val in value])) else: values_display.append(choices_dict.get(value, 'None')) except ValueError: values_display.append(choices_dict.get(value, 'None')) except: values_display.append(choices_dict.get(value, 'None')) else: try: field_type = field.get_internal_type() except AttributeError: # if the field is a relationship it has no internal type and exclude it continue for value in values: # handle case where field is a datetime, date, or time type if field_type in ["DateTimeField", "DateField", "TimeField"]: try: value = parser.parse(value) if field_type == "DateField": value = value.date() elif field_type == "TimeField": value = value.time() elif field_type == "DateTimeField": value = value.replace(tzinfo=timezone.utc) value = value.astimezone(gettz(settings.TIME_ZONE)) value = formats.localize(value) except ValueError: pass # check if length is longer than 140 and truncate with ellipsis if len(value) > 140: value = "{}...".format(value[:140]) values_display.append(value) verbose_name = model_fields['mapping_fields'].get(field.name, getattr(field, 'verbose_name', field.name)) changes_display_dict[verbose_name] = values_display return changes_display_dict
def model_instance_diff(old, new, **kwargs): """ Calculate the differences between two model instances. One of the instances may be None (i.e., a newly created model or deleted model). This will cause all fields with a value to have changed (from None). """ from auditlog.registry import auditlog if not (old is None or isinstance(old, Model)): raise TypeError( "The supplied old instance is not a valid model instance.") if not (new is None or isinstance(new, Model)): raise TypeError( "The supplied new instance is not a valid model instance.") diff = {} if old is not None and new is not None: fields = set(old._meta.fields + new._meta.fields) model_fields = auditlog.get_model_fields(new._meta.model) elif old is not None: fields = set(old._meta.fields) model_fields = auditlog.get_model_fields(old._meta.model) elif new is not None: fields = set(new._meta.fields) model_fields = auditlog.get_model_fields(new._meta.model) else: fields = set() model_fields = None # Check if fields must be filtered if model_fields and (model_fields['include_fields'] or model_fields['exclude_fields']) and fields: filtered_fields = [] if model_fields['include_fields']: filtered_fields = [ field for field in fields if field.name in model_fields['include_fields'] ] else: filtered_fields = fields if model_fields['exclude_fields']: filtered_fields = [ field for field in filtered_fields if field.name not in model_fields['exclude_fields'] ] fields = filtered_fields for field in fields: try: old_value = smart_text(getattr(old, field.name, None)) except ObjectDoesNotExist: old_value = None try: new_value = smart_text(getattr(new, field.name, None)) except ObjectDoesNotExist: new_value = None if old_value != new_value: diff[field.name] = (old_value, new_value) if len(diff) == 0: diff = None return diff
def changes_display_dict(self): """ :return: The changes recorded in this log entry intended for display to users as a dictionary object. """ # Get the model and model_fields from auditlog.registry import auditlog model = self.content_type.model_class() model_fields = auditlog.get_model_fields(model._meta.model) changes_display_dict = {} # grab the changes_dict and iterate through for field_name, values in self.changes_dict.items(): # try to get the field attribute on the model try: field = model._meta.get_field(field_name) except FieldDoesNotExist: changes_display_dict[field_name] = values continue values_display = [] # handle choices fields and Postgres ArrayField to get human readable version choices_dict = None if getattr(field, "choices") and len(field.choices) > 0: choices_dict = dict(field.choices) if ( hasattr(field, "base_field") and isinstance(field.base_field, Field) and getattr(field.base_field, "choices") and len(field.base_field.choices) > 0 ): choices_dict = dict(field.base_field.choices) if choices_dict: for value in values: try: value = ast.literal_eval(value) if type(value) is [].__class__: values_display.append( ", ".join( [choices_dict.get(val, "None") for val in value] ) ) else: values_display.append(choices_dict.get(value, "None")) except ValueError: values_display.append(choices_dict.get(value, "None")) except: values_display.append(choices_dict.get(value, "None")) else: try: field_type = field.get_internal_type() except AttributeError: # if the field is a relationship it has no internal type and exclude it continue for value in values: # handle case where field is a datetime, date, or time type if field_type in ["DateTimeField", "DateField", "TimeField"]: try: value = parser.parse(value) if field_type == "DateField": value = value.date() elif field_type == "TimeField": value = value.time() elif field_type == "DateTimeField": value = value.replace(tzinfo=timezone.utc) value = value.astimezone(gettz(settings.TIME_ZONE)) value = formats.localize(value) except ValueError: pass # check if length is longer than 140 and truncate with ellipsis if len(value) > 140: value = "{}...".format(value[:140]) values_display.append(value) verbose_name = model_fields["mapping_fields"].get( field.name, getattr(field, "verbose_name", field.name) ) changes_display_dict[verbose_name] = values_display return changes_display_dict
def model_instance_diff(old, new): """ Calculates the differences between two model instances. One of the instances may be ``None`` (i.e., a newly created model or deleted model). This will cause all fields with a value to have changed (from ``None``). :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 """ from auditlog.registry import auditlog if not(old is None or isinstance(old, Model)): raise TypeError("The supplied old instance is not a valid model instance.") if not(new is None or isinstance(new, Model)): raise TypeError("The supplied new instance is not a valid model instance.") diff = {} if old is not None and new is not None: fields = set(old._meta.fields + new._meta.fields) model_fields = auditlog.get_model_fields(new._meta.model) elif old is not None: fields = set(get_fields_in_model(old)) model_fields = auditlog.get_model_fields(old._meta.model) elif new is not None: fields = set(get_fields_in_model(new)) model_fields = auditlog.get_model_fields(new._meta.model) else: fields = set() model_fields = None # Check if fields must be filtered if model_fields and (model_fields['include_fields'] or model_fields['exclude_fields']) and fields: filtered_fields = [] if model_fields['include_fields']: filtered_fields = [field for field in fields if field.name in model_fields['include_fields']] else: filtered_fields = fields if model_fields['exclude_fields']: filtered_fields = [field for field in filtered_fields if field.name not in model_fields['exclude_fields']] fields = filtered_fields for field in fields: try: old_value = smart_text(getattr(old, field.name, None)) except ObjectDoesNotExist: old_value = field.default if field.default is not NOT_PROVIDED else None try: new_value = smart_text(getattr(new, field.name, None)) except ObjectDoesNotExist: new_value = None if old_value != new_value: diff[field.name] = (smart_text(old_value), smart_text(new_value)) if len(diff) == 0: diff = None return diff