示例#1
0
def create_notification_based_on_task_changes(sender, instance, *args,
                                              **kwargs):
    """Notifies the user that something has changed in the task"""
    if get_current_user().is_anonymous:
        return

    # refresh task from DB
    instance = Task.objects.prefetch_common().get(pk=instance.pk)

    assigned_users = instance.assigned_users.all().exclude(
        pk=get_current_user().pk)

    for assigned_user in assigned_users:
        context = {'user': assigned_user, 'instance': instance}
        html_message = render_to_string('notification/task_changed.html',
                                        context)

        # check if a notification for this task and the assigned user has already been created by the current user
        # within the last 60 secs and if it is unread
        Notification.objects.update_or_create(
            user=assigned_user,
            content_type=instance.get_content_type(),
            object_id=instance.pk,
            read=False,
            sent__isnull=True,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_TASK_CHANGED,
            created_by=get_current_user(),
            created_at__gte=timezone.now() - timedelta(seconds=60),
            defaults={
                'title':
                _("Task {title} has changed".format(title=instance.title)),
                'message': html_message,
                'created_at': timezone.now(),
            })
示例#2
0
def create_notification_based_on_delete_user_from_project(
        sender, instance, *args, **kwargs):
    """Notifies the attended user that he was removed from the project"""
    if get_current_user().is_anonymous:
        return

    assigned_user = instance.user

    # check that the role of this instance has the view_project role
    if not instance.role.permissions.filter(
            codename='view_project',
            content_type=Project.get_content_type()).exists():
        # this role does not have view_project, so we are not sending a notification
        return

    if assigned_user != get_current_user():
        project = instance.project

        # render html message
        context = {'user': assigned_user, 'instance': project}
        html_message = render_to_string(
            'notification/project_remove_user.html', context)

        Notification.objects.create(
            user=assigned_user,
            title=_("You have been removed from project {project}").format(
                project=project.name),
            message=html_message,
            content_type=project.get_content_type(),
            object_id=project.pk,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_PROJECT_USER_CHANGED)
示例#3
0
def create_notification_based_on_meeting_changes(sender, instance, *args,
                                                 **kwargs):
    """Notifies the user that something has changed in the meeting"""
    if get_current_user().is_anonymous:
        return

    # save the instance here before the refresh, so it can be used to determine the date_time_start
    # for scheduled notifications
    to_be_saved_instance = instance

    # refresh meeting from DB
    instance = Meeting.objects.prefetch_common().get(pk=instance.pk)

    # if there's a ScheduledNotification related to this meeting,
    # it's deletion-status needs to be updated based on the meeting's status
    try:
        scheduled_notification = ScheduledNotification.objects.get(
            object_id=instance.pk)
        # Don't send reminder notifications if the to_be_saved meeting date_time_start is already in the past
        if scheduled_notification and to_be_saved_instance.local_date_time_start > timezone.now(
        ):
            scheduled_notification.scheduled_date_time = ScheduledNotification.calculate_scheduled_date_time(
                scheduled_notification.timedelta_unit,
                scheduled_notification.timedelta_value,
                instance.date_time_start)
            scheduled_notification.deleted = instance.deleted
            # make sure the reminder is sent again after meeting details have changed
            scheduled_notification.processed = False
            scheduled_notification.save()
    except ScheduledNotification.DoesNotExist:
        pass

    attending_users = instance.attending_users.all().exclude(
        pk=get_current_user().pk)

    for attended_user in attending_users:
        context = {'user': attended_user, 'instance': instance}
        html_message = render_to_string('notification/meeting_changed.html',
                                        context)

        Notification.objects.update_or_create(
            user=attended_user,
            content_type=instance.get_content_type(),
            object_id=instance.pk,
            read=False,
            sent__isnull=True,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_MEETING_CHANGED,
            created_by=get_current_user(),
            created_at__gte=timezone.now() - timedelta(seconds=60),
            defaults={
                'title':
                _("Appointment {title} has changed").format(
                    title=instance.title),
                'message':
                html_message,
                'created_at':
                timezone.now()
            })
示例#4
0
    def save(self, *args, **kwargs):
        if self.pk is None:
            super().save(*args, **kwargs)
            user = get_current_user()

            if not user.is_anonymous:
                UserTeam.objects.create(user=get_current_user(),
                                        team=self,
                                        role=UserTeam.ROLE_EDITOR)
        else:
            super().save(*args, **kwargs)
示例#5
0
 def editable(self, *args, **kwargs):
     """
     The current user may update its own notifications (e.g., read)
     and the user that created a notification may update it
     :param args:
     :param kwargs:
     :return:
     """
     return self.filter(
         Q(user=get_current_user()) |
         Q(created_by=get_current_user())
     )
示例#6
0
 def set_deleted(self, deleted=True):
     if deleted:
         self.deleted_via_caldav_on = now()
         self.deleted_via_caldav_by = get_current_user()
     else:
         self.deleted_via_caldav_on = None
         self.deleted_via_caldav_by = None
示例#7
0
    def create(self, validated_data):
        """ Creates a copy of a contact for another user. """

        # take created_for field out of validated data
        receiving_user = validated_data.pop('created_for')
        sending_user = get_current_user()

        # pass usual data to standard contact serializer
        instance = super().create(validated_data)

        # hand over access to the receiving user
        if receiving_user != sending_user:
            # add access privilege for the receiving user (created_for field)
            from eric.model_privileges.models import ModelPrivilege
            ModelPrivilege.objects.update_or_create(
                user=receiving_user,
                content_type=instance.get_content_type(),
                object_id=instance.pk,
                defaults={
                    'full_access_privilege': ModelPrivilege.ALLOW,
                })

            # remove access privilege from self
            initial_privilege = ModelPrivilege.objects.filter(
                user=sending_user,
                object_id=instance.pk,
                content_type=instance.get_content_type(),
            )
            initial_privilege.delete()

        return instance
示例#8
0
    def create(self, validated_data):
        user = get_current_user()
        validated_data['user_id'] = user.id

        instance = super().create(validated_data)

        return instance
示例#9
0
    def process_response(self, request, response):
        s = getattr(response, 'status_code', 0)
        r = "by %s, status %s, " % (str(get_current_user()), str(s))
        if s in (300, 301, 302, 307):
            r += ' redirect to %s' % response.get('Location', '?')
        elif hasattr(response, "content") and response.content:
            r += ' sent %d bytes' % len(response.content)
        elif hasattr(response, "streaming_content") and response.streaming_content:
            r += ' streaming / downloading'

        # if status code is 2xx and debug mode is activated
        if 200 <= s <= 299 and settings.DEBUG:
            total_time = 0

            for query in connection.queries:
                query_time = query.get('time')
                if query_time is None:
                    # django-debug-toolbar monkeypatches the connection
                    # cursor wrapper and adds extra information in each
                    # item in connection.queries. The query time is stored
                    # under the key "duration" rather than "time" and is
                    # in milliseconds, not seconds.
                    query_time = query.get('duration', 0) / 1000
                total_time += float(query_time)

            r += ', %d queries, %0.4f seconds' % (len(connection.queries), total_time)

        self.log_message(request, 'response', r)
        return response
示例#10
0
def auto_create_owner_entity_permission(instance, created, *args, **kwargs):
    """
    Automatically creates an entity permission assignment with "is_owner = True" for a given entity,
    if "can_have_special_permissions = True" is set for the model
    :return:
    """
    # ignore raw inserts (e.g. from fixtures) and updates (not created)
    if kwargs.get('raw') or not created:
        return

    # ignore elements that do not have can_have_special_permissions
    if not hasattr(instance._meta, "can_have_special_permissions"):
        return

    # ignore can_have_special_permissions = False
    if not instance._meta.can_have_special_permissions:
        return

    current_user = get_current_user()

    if not current_user or current_user.is_anonymous:
        logger.warning("In auto_create_owner_entity_permission: current_user is anonymous, "
                       "not creating entity permission assignment")
        return

    # now can_have_special_permissions = True, and we need to create the assignment
    ModelPrivilege.objects.create(
        user=current_user,
        full_access_privilege=ModelPrivilege.ALLOW,
        content_object=instance
    )
示例#11
0
    def perform_create(self, serializer):
        """
        Ensure that the current user is always attending the meeting they created, unless the current user creates
        a meeting for another user through calendar access privileges

        We need to do this here (rather than in a pre_save/post_save handler)
        """
        instance = serializer.save()

        if instance.create_for:
            User = get_user_model()
            create_for_user = User.objects.filter(
                pk=instance.create_for).first()
            if create_for_user:
                # add the create_for_user to attending users here
                UserAttendsMeeting.objects.get_or_create(
                    meeting=instance,
                    user=create_for_user,
                )
                # also give the create_for_user full access privileges for the meeting
                ModelPrivilege.objects.get_or_create(
                    content_type=Meeting.get_content_type(),
                    object_id=instance.pk,
                    user=create_for_user,
                    full_access_privilege=ModelPrivilege.ALLOW)
        else:
            UserAttendsMeeting.objects.get_or_create(
                meeting=instance,
                user=get_current_user(),
            )
示例#12
0
def ms_office_cleanup_sequence(sender, instance, *args, **kwargs):
    """
    Detects when a MS Office File is edited in WebDav and starts a cleanup sequence that:
    1) When a file is just opened in MS Office the lock will be removed. Also a cleanup happens where
    unneeded tmp files are deleted
    2) When a file is being edited in MS Office the lock on the original will be set.
    3) When a file is saved in MS Office the original will be untrashed, a new file an entry will be created
    with the content of the last tmp save
    """
    # check if this is a file that can be cleaned up, if not just return before doing anything else
    if not has_ms_office_extension(instance.name):
        return

    # get the user of the request
    user = get_current_user()

    # get a list of Q queries with all extensions, that can later be used in filters.
    clauses = (Q(original_filename__endswith=extension)
               for extension in MS_OFFICE_EXTENSIONS)
    extension_query = reduce(operator.or_, clauses)

    # a file is opened or closed
    if instance.name.startswith(OFFICE_TEMP_FILE_PREFIX) and instance.deleted:
        handle_opened_or_closed_ms_office_file(instance, user, extension_query)

    # a file is being edited
    if instance.name.startswith(
            OFFICE_TEMP_FILE_PREFIX) and not instance.deleted:
        handle_edited_ms_office_file(instance, user)

    # a file is being saved by ms office
    if instance.name.endswith('.tmp') and instance.deleted:
        handle_saved_ms_office_file(instance, extension_query)
示例#13
0
    def viewable(self):
        from teams.models import UserTeam
        user = get_current_user()

        return self.filter(
            Q(user=user, ) | Q(team__in=UserTeam.objects.filter(
                user=user).values_list('team', flat=True)))
示例#14
0
def check_soft_delete_and_restore_roles(mng, instance, old_instance):
    """
    Checks for soft delete and trash roles of the provided instance
    :param instance: SoftDeleteMixin
    """
    user = get_current_user()

    # find out whether this is a restore or trash operation

    if old_instance.deleted:
        assert instance.deleted is False
        # restore instance

        # check if user has global restore permission
        if user.has_perm(get_permission_name(instance.__class__, 'restore')):
            return

        # check if object is restorable according to the objects manager
        if hasattr(instance, 'is_restorable') and not instance.is_restorable():
            raise PermissionDenied

    else:
        assert instance.deleted is True
        # trash instance

        # check if user has global trash permission
        if user.has_perm(get_permission_name(instance.__class__, 'trash')):
            return

        # check if object is trashable according to the objects manager
        if hasattr(instance, 'is_trashable') and not instance.is_trashable():
            raise PermissionDenied
示例#15
0
def check_update_project_parent_project(instance, *args, **kwargs):
    """
    Checks whether the current user is allowed to change the parent project of the given project instance
    :param instance:
    :param args:
    :param kwargs:
    :return:
    """
    # do not check for raw inserts or ChangeSet or ChangeRecord insert - those are always allowed
    if kwargs.get('raw') or isinstance(instance, ChangeSet) or isinstance(
            instance, ChangeRecord):
        return

    if permission_checks_disabled(instance):
        return

    mng = instance.__class__.objects

    # check if instance exists
    if mng.filter(pk=instance.pk).count() == 0:
        # new instance --> ignore (check_create_roles will handle that case)
        return

    # check whether the current user is allowed to do stuff with the current project
    # therefore we need to remove the original sender
    kwargs['original_sender'] = kwargs.pop('sender')
    check_create_roles_for_parent_project(get_current_user(), Project,
                                          instance, *args, **kwargs)
示例#16
0
def check_delete_roles(sender, instance, *args, **kwargs):
    """
    On pre_delete, check delete permission for every database object where the queryset implements "deletable"
    raises a PermissionDenied exception on error
    """
    if isinstance(instance, Metadata):
        # deleting meta data is fine
        return

    if permission_checks_disabled(instance):
        return

    # ensure that the object has been soft deleted already, else raise an exception
    raise_error_if_object_not_soft_deleted(sender, instance, *args, **kwargs)

    user = get_current_user()

    # check if user has global delete permission for this class
    if user.has_perm(get_permission_name(instance.__class__, 'delete')):
        return

    mng = instance.__class__.objects

    # check if deletable exists and the instance.pk is in the deletable queryset
    if hasattr(mng, "deletable") and callable(
            mng.deletable) and mng.deletable().filter(
                pk=instance.pk).count() == 0:
        raise PermissionDenied
示例#17
0
    def prevent_dmp_form_change(self):
        """
        checks if the dmp exists before or not. It is only created when it does not exist.
        """

        # check if object exists (= update) or if it does not exist (= create)
        dmp_object = Dmp.objects.filter(id=self.pk).first()
        if not dmp_object:
            # on create --> no need to do anything
            return

        # else: it's an update, check that dmp_form has not changed
        if dmp_object.dmp_form_id != self.dmp_form_id:
            raise ValidationError({
                'dmp_form':
                ValidationError(
                    _('You are not allowed to change the dmp form'),
                    params={'dmp': self},
                    code='invalid')
            })

            # check if the status is set to FINAL and the user is not the creator
            # True --> DMP Form Data can not be changed
            # False --> DMP From Data can be
        if dmp_object.status == Dmp.FINAL and dmp_object.created_by != get_current_user(
        ):
            raise ValidationError({
                'status':
                ValidationError(_(
                    'Once the status is set to final, updates are only allowed by the user that created the DMP.'
                ),
                                params={'dmp': self},
                                code='invalid')
            })
示例#18
0
    def viewable(self, *args, **kwargs):
        """
        Returns all elements where the current user is attending,
        the current user is the organizer,
        or recurring CalDav items where the current user is the creator
        """
        from eric.shared_elements.models import Meeting
        user = get_current_user()

        return self.filter(
            Q(
                # all CaldavItems where the current user is attending
                meeting__pk__in=Meeting.objects.attending().values_list('pk')
            ) | Q(
                # all CaldavItems where the current user is the organizer
                meeting__created_by=user
            ) | Q(
                # all CaldavItems where meeting is null
                # currently needed to sync recurring items, as they have no meeting, but should still be synced
                created_by=user,
                meeting__isnull=True,
                text__icontains='rrule'
            ) | Q(
                # all CaldavItems where meeting is null
                # currently needed to sync recurring items, as they have no meeting, but should still be synced
                created_by=user,
                meeting__isnull=True,
                text__icontains='recurrence-id'
            )
        )
示例#19
0
    def get_filtered_schedule_elements(self):
        show_tasks = self.request.query_params.get('show_tasks', 1)
        show_meetings = self.request.query_params.get('show_meetings', 1)

        show_meetings_for = self.request.query_params.getlist(
            'show_meetings_for', None)

        tasks = Task.objects.none()
        meetings = Meeting.objects.none()

        if show_tasks == 1 or show_tasks == '1':
            # filter all viewable tasks, that are assigned to the current user, and that have a start and due date
            tasks = self.get_tasks_queryset()
            # overwrite filter class for tasks
            self.filterset_class = TaskFilter
            tasks = self.filter_queryset(tasks)

        if show_meetings == 1 or show_meetings == '1':
            # filter all viewable meetings, that are attending to the current user
            if show_meetings_for:
                meetings = self.get_meetings_queryset(show_meetings_for)
            # for the ical_export we need to set the user
            else:
                current_user = get_current_user()
                meetings = self.get_meetings_queryset(str(current_user.pk))

        return tasks, meetings
示例#20
0
    def editable(self):
        from teams.models import UserTeam

        user = get_current_user()
        return self.filter(
            team__user_teams__user=user,
            team__user_teams__role=UserTeam.ROLE_EDITOR).distinct()
示例#21
0
    def usable(self):
        """
        returns all elements of the model where
        - the element has user_availability set to global
        - the element has the current user in user_availability_selected_users
        - the element has at least one user group of the current user in user_availability_selected_user_groups
        """

        user = get_current_user()
        if user.is_anonymous:
            return self.none()
        elif user.is_superuser:
            return self.all()

        from eric.plugins.models import Plugin

        return self.filter(
            Q(
                # all plugins where user_availability is set to global
                user_availability=Plugin.GLOBAL) | Q(
                    # all plugins where the current user is selected
                    user_availability_selected_users=user)
            | Q(
                # all plugins where the user group of the current user is selected
                user_availability_selected_user_groups__pk__in=user.groups.
                values_list('pk'))).distinct()
示例#22
0
 def get_meetings_queryset(self, show_meetings_for):
     qs = Meeting.objects.none()
     current_user = get_current_user()
     # overwrite filter class for meetings
     self.filterset_class = MeetingFilter
     if str(current_user.pk) in show_meetings_for:
         # this is the queryset for the current user, which uses viewable and attending
         qs = Meeting.objects.viewable().prefetch_common().attending(
         ).prefetch_related('projects', ).filter(
             deleted=False,
             date_time_start__isnull=False,
             date_time_end__isnull=False)
         # we have to filter the queryset here before the union as filtering on unions isnt supported
         qs = self.filter_queryset(qs)
     # this is the queryset for users other than the current user, which uses viewable()
     qs_extended = Meeting.objects.viewable().prefetch_common(
     ).prefetch_related(
         'projects', ).exclude(attending_users=current_user).filter(
             attending_users__in=show_meetings_for,
             deleted=False,
             date_time_start__isnull=False,
             date_time_end__isnull=False)
     # we have to filter the queryset here before the union as filtering on unions isnt supported
     qs_extended = self.filter_queryset(qs_extended)
     # now we can build the union
     qs = qs.union(qs_extended)
     return qs
示例#23
0
    def deletable(self):
        user = get_current_user()

        if user.has_perm('projects.delete_role'):
            return self.all()

        return self.none()
示例#24
0
    def get_queryset(self):
        """
        gets a queryset with all changes to the items that the current user has access to
        :return:
        """
        user = get_current_user()

        if user.is_anonymous:
            return ChangeSet.objects.none()

        # build a conditions list, where we will add more conditions with "OR"
        conditions = Q()

        # get all relevant search models
        workbench_models = self.get_models_based_on_request(self.request)

        # temporary fix: load ChangeSets for single models only, to avoid excessive performance hits
        # TODO: Fix underlying performance problem. Suspect #1: .prefetch_related('change_records')
        if len(workbench_models) != 1:
            return ChangeSet.objects.none()

        # iterate over search models
        for model in workbench_models:
            # add conditions to existing conditions with OR
            conditions = conditions | Q(
                object_type=model.get_content_type(),
                object_uuid__in=model.objects.viewable())

        # query changesets with above conditions
        return ChangeSet.objects.filter(
            conditions).order_by('-date').select_related(
                'user', 'user__userprofile',
                'object_type').prefetch_related('change_records')
示例#25
0
    def editable(self):
        user = get_current_user()

        if user.has_perm('projects.change_role'):
            return self.all()

        return self.none()
示例#26
0
    def lock(self, webdav=False):
        """
        Lock an element
        :return:
        """
        # check if a lock exists already
        lock = self.get_lock_element().first()
        # update the lock if it does exist
        if lock:
            if lock.webdav_lock:
                webdav = True
            lock = self.update_lock(lock, webdav=webdav)
        # create a new one if it does not exist
        else:
            from eric.projects.models import ElementLock

            lock = ElementLock.objects.create(
                object_id=self.pk,
                content_type=self.get_content_type(),
                locked_by=get_current_user(),
                webdav_lock=webdav,
            )

        # remove all existing locks for this element
        self.remove_all_locks(except_pk_list=[lock.pk])

        return lock
示例#27
0
    def updated_by_current_user(self, *args, **kwargs):
        """
        returns all objects that have been updated by the user (based on the django changeset model)
        """
        user = get_current_user()

        return self.filter(last_modified_by=user)
示例#28
0
    def _editable():
        from eric.shared_elements.models import Task
        user = get_current_user()

        # get all tasks that the current user is assigned to
        task_pks = Task.objects.filter(assigned_users=user).values_list('pk')

        return Q(pk__in=task_pks)
示例#29
0
    def my_bookings(self):
        """ Own bookings for an accessible resource """
        from eric.projects.models.models import Resource

        return self.viewable().filter(
            resource__in=Resource.objects.viewable(),
            created_by=get_current_user(),
        )
示例#30
0
 def get_queryset(self):
     """
     Gets all viewable notifications for the current user.
     """
     return Notification.objects.viewable().filter(
         user=get_current_user()).select_related(
             'content_type', 'created_by', 'created_by__userprofile',
             'last_modified_by', 'last_modified_by__userprofile')
示例#31
0
    def is_staff_or_created_by_current_user(self, *args, **kwargs):
        """
        returns all objects that have been created by the user (or if staff, all)
        """
        user = get_current_user()

        if user.is_staff:
            return self.all()
        else:
            return self.created_by_current_user(args, kwargs)
    def pre_save(self, model_instance, add):
        object_user = self.value_from_object(model_instance)
        current_user = get_current_user()

        if current_user.pk and (self.auto_user or (self.auto_user_add and add and not object_user)):
            setattr(model_instance, self.name, current_user)
        elif not current_user.pk:
            # log if the current user cannot be referenced in DB (e.g. AnonymousUser)
            logger.info(u"Cannot save reference for non-persistent user")

        return super(UserForeignKey, self).pre_save(model_instance, add)
示例#33
0
    def save_model_revision(sender, **kwargs):
        if not RevisionModelMixin.get_enabled():
            return

        # do not track raw inserts/updates (e.g. fixtures)
        if kwargs.get('raw'):
            return

        new_instance = kwargs['instance']

        # check if this is a revision model
        if not new_instance.pk or not isinstance(new_instance, RevisionModelMixin):
            return

        changed_fields = new_instance.changed_data

        # quit here if there is nothing to track.
        if not changed_fields:
            return

        # determine whether this is a soft delete or a restore operation
        is_soft_delete = False
        is_restore = False

        # get track_soft_deleted_by from the current model
        track_soft_delete_by = getattr(new_instance._meta, 'track_soft_delete_by', None)
        if track_soft_delete_by and track_soft_delete_by in changed_fields:
            # if len(changed_fields) > 1:
            #     raise Exception("""Can not modify more than one field if track_soft_delete_by is changed""")

            # determine whether this is a soft delete or a trash
            change_record = changed_fields[track_soft_delete_by]

            # ToDo: Why are we accessing [1] here?
            if change_record[1] is True:
                is_soft_delete = True
            else:
                is_restore = True

        object_uuid_field_name = getattr(new_instance._meta, 'track_by', 'id')
        object_uuid_field = new_instance._meta.get_field(object_uuid_field_name)
        content_type = ContentType.objects.get_for_model(new_instance)

        change_set = ChangeSet()

        change_set.object_type = content_type

        if isinstance(object_uuid_field, models.UUIDField):
            change_set.object_uuid = getattr_orm(new_instance, object_uuid_field_name)

        else:
            change_set.object_id = getattr_orm(new_instance, object_uuid_field_name)

        # are there any existing changesets (without restore/soft_delete)?
        existing_changesets = ChangeSet.objects.filter(
            object_uuid=change_set.object_uuid,
            object_type=content_type
        ).exclude(
            changeset_type__in=[ChangeSet.RESTORE_TYPE, ChangeSet.SOFT_DELETE_TYPE]
        )

        last_changeset = None

        update_existing_changeset = False

        if existing_changesets.exists():
            # an existing changeset already exists
            # the operation performed can be either soft delete, restore or update
            if is_soft_delete:
                change_set.changeset_type = change_set.SOFT_DELETE_TYPE
            elif is_restore:
                change_set.changeset_type = change_set.RESTORE_TYPE
            else:
                change_set.changeset_type = change_set.UPDATE_TYPE
                # get the latest changeset, so we can check if the latest of existing_changeset was created by the
                # current user within the last couple of seconds
                last_changeset = existing_changesets.latest()

        # check if last changeset was created by the current user within the last couple of seconds
        if last_changeset \
                and last_changeset.user == get_current_user() \
                and last_changeset.date > timezone.now() - timezone.timedelta(
            seconds=getattr(new_instance._meta, 'aggregate_changesets_within_seconds', 0)
        ):
            # overwrite the new_changeset
            logger.debug("Re-using last changeset")
            change_set = last_changeset
            change_set.date = timezone.now()
            update_existing_changeset = True

        change_set.save()

        if update_existing_changeset:
            # updateing an existing changeset: need to check all change records for their existance
            check_number_of_change_records = False

            for changed_field, changed_value in changed_fields.items():
                # if the changerecord for a change_set and a field already exists, it needs to be updated
                change_record, created = ChangeRecord.objects.get_or_create(
                    change_set=change_set, field_name=changed_field,
                    defaults={'old_value': changed_value[0], 'new_value': changed_value[1]},
                )

                if not created:
                    # it already exists, therefore we need to update new value
                    change_record.new_value = changed_value[1]

                    # check if old value and new value are the same
                    if change_record.new_value == change_record.old_value:
                        # delete this change record
                        change_record.delete()
                        check_number_of_change_records = True
                    else:
                        # save this change record
                        change_record.save()

            # we deleted a change record, lets make sure a change record still exists
            if check_number_of_change_records:
                if not change_set.change_records.all().exists():
                    # no change record exists for this changeset --> delete the change set
                    change_set.delete()

        else:
            # collect change records
            change_records = []

            # iterate over all changed fields and create a change record for them
            for changed_field, changed_value in changed_fields.items():
                change_record = ChangeRecord(
                    change_set=change_set, field_name=changed_field,
                    old_value=changed_value[0], new_value=changed_value[1]
                )
                change_records.append(change_record)

            # do a bulk create to increase database performance
            ChangeRecord.objects.bulk_create(change_records)

        RevisionModelMixin.save_related_revision(sender, **kwargs)