Repository: arenadata/adcm (language: python)

Issue:
ADCM-3052 Fix audit object deletion



Discussion / hints:

Fix patch:
diff --git a/python/audit/middleware.py b/python/audit/middleware.py
index 3df8a6221a..7e56d031a5 100644
--- a/python/audit/middleware.py
+++ b/python/audit/middleware.py
@@ -26,7 +26,6 @@ def __call__(self, request):
 
         body = json.loads(request.body.decode('utf-8'))
         username = body['username']
-
         response = self.get_response(request)
 
         if request.user.is_authenticated:
@@ -44,5 +43,7 @@ def __call__(self, request):
                     result = AuditSessionLoginResult.WrongPassword
             except User.DoesNotExist:
                 result = AuditSessionLoginResult.UserNotFound
+
         AuditSession.objects.create(user=audit_user, login_result=result, login_details=details)
+
         return response
diff --git a/python/audit/models.py b/python/audit/models.py
index f62a27bf90..a4a37159f9 100644
--- a/python/audit/models.py
+++ b/python/audit/models.py
@@ -12,8 +12,18 @@
 
 from dataclasses import dataclass
 
-from django.contrib.auth.models import User
+from cm.models import (
+    ADCM,
+    Bundle,
+    Cluster,
+    ClusterObject,
+    Host,
+    HostProvider,
+    ServiceComponent,
+)
+from django.contrib.auth.models import User as DjangoUser
 from django.db import models
+from rbac.models import Group, Policy, Role, User
 
 
 class AuditObjectType(models.TextChoices):
@@ -68,12 +78,12 @@ class AuditLog(models.Model):
     operation_type = models.CharField(max_length=16, choices=AuditLogOperationType.choices)
     operation_result = models.CharField(max_length=16, choices=AuditLogOperationResult.choices)
     operation_time = models.DateTimeField(auto_now_add=True)
-    user = models.ForeignKey(User, on_delete=models.CASCADE)
+    user = models.ForeignKey(DjangoUser, on_delete=models.CASCADE)
     object_changes = models.JSONField(default=dict)
 
 
 class AuditSession(models.Model):
-    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
+    user = models.ForeignKey(DjangoUser, on_delete=models.CASCADE, null=True)
     login_result = models.CharField(max_length=64, choices=AuditSessionLoginResult.choices)
     login_time = models.DateTimeField(auto_now_add=True)
     login_details = models.JSONField(default=dict, null=True)
@@ -83,3 +93,18 @@ class AuditSession(models.Model):
 class AuditOperation:
     name: str
     operation_type: str
+
+
+AUDIT_OBJECT_TYPE_TO_MODEL_MAP = {
+    Cluster: AuditObjectType.Cluster,
+    ClusterObject: AuditObjectType.Service,
+    ServiceComponent: AuditObjectType.Component,
+    Host: AuditObjectType.Host,
+    HostProvider: AuditObjectType.Provider,
+    Bundle: AuditObjectType.Bundle,
+    ADCM: AuditObjectType.ADCM,
+    User: AuditObjectType.User,
+    Group: AuditObjectType.Group,
+    Role: AuditObjectType.Role,
+    Policy: AuditObjectType.Policy,
+}
diff --git a/python/audit/utils.py b/python/audit/utils.py
index ad024d89dc..ede8ba8e86 100644
--- a/python/audit/utils.py
+++ b/python/audit/utils.py
@@ -47,7 +47,7 @@
 
 def _get_audit_object_from_resp(res: Response, obj_type: str) -> Optional[AuditObject]:
     if res and res.data and res.data.get("id") and res.data.get("name"):
-        audit_object, _ = AuditObject.objects.get_or_create(
+        audit_object = _get_or_create_audit_obj(
             object_id=res.data["id"],
             object_name=res.data["name"],
             object_type=obj_type,
@@ -58,45 +58,6 @@ def _get_audit_object_from_resp(res: Response, obj_type: str) -> Optional[AuditO
     return audit_object
 
 
-def make_audit_log(operation_type, result, operation_status):
-    operation_type_map = {
-        "task_db": {
-            "type": AuditLogOperationType.Delete,
-            "name": '"Task log cleanup in database on schedule" job',
-        },
-        "task_fs": {
-            "type": AuditLogOperationType.Delete,
-            "name": '"Task log cleanup in filesystem on schedule" job',
-        },
-        "config": {
-            "type": AuditLogOperationType.Delete,
-            "name": '"Objects configurations cleanup on schedule" job',
-        },
-        "sync": {"type": AuditLogOperationType.Update, "name": '"User sync on schedule" job'},
-        "audit": {
-            "type": AuditLogOperationType.Delete,
-            "name": '"Audit log cleanup/archiving on schedule" job',
-        },
-    }
-    result = (
-        AuditLogOperationResult.Success if result == 'success' else AuditLogOperationResult.Fail
-    )
-    operation_name = operation_type_map[operation_type]["name"] + ' ' + operation_status
-    audit_object, _ = AuditObject.objects.get_or_create(
-        object_id=ADCM.objects.get().id,
-        object_name='ADCM',
-        object_type=AuditObjectType.ADCM,
-    )
-    system_user = User.objects.get(username='system')
-    AuditLog.objects.create(
-        audit_object=audit_object,
-        operation_name=operation_name,
-        operation_type=operation_type_map[operation_type]['type'],
-        operation_result=result,
-        user=system_user,
-    )
-
-
 def _get_object_type_from_resp(audit_operation: AuditOperation, resp: Response) -> str:
     if audit_operation.object_type == "config log":
         object_type: str = ContentType.objects.get_for_model(
@@ -129,7 +90,7 @@ def _task_case(task_pk: str, action: str) -> Tuple[AuditOperation, AuditObject]:
         name=f"{obj_type} {action_name} {action}ed",
         operation_type=AuditLogOperationType.Update,
     )
-    audit_object, _ = AuditObject.objects.get_or_create(
+    audit_object = _get_or_create_audit_obj(
         object_id=task_pk,
         object_name=obj.task_object.name,
         object_type=obj.object_type.name,
@@ -157,6 +118,22 @@ def _get_obj_type(obj_type: str) -> str:
     return obj_type
 
 
+def _get_or_create_audit_obj(object_id: int, object_name: str, object_type: str) -> AuditObject:
+    audit_object = AuditObject.objects.filter(
+        object_id=object_id,
+        object_type=object_type,
+    ).first()
+
+    if not audit_object:
+        audit_object = AuditObject.objects.create(
+            object_id=object_id,
+            object_name=object_name,
+            object_type=object_type,
+        )
+
+    return audit_object
+
+
 # pylint: disable-next=too-many-statements,too-many-branches,too-many-locals
 def _get_audit_operation_and_object(
         view: View, res: Response, deleted_obj: Model
@@ -185,7 +162,7 @@ def _get_audit_operation_and_object(
                 name=f"{AuditObjectType.Bundle.capitalize()} {AuditLogOperationType.Delete}d",
                 operation_type=AuditLogOperationType.Delete,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=bundle_pk,
                 object_name=deleted_obj.name,
                 object_type=AuditObjectType.Bundle,
@@ -197,7 +174,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Bundle.objects.get(pk=bundle_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=bundle_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Bundle,
@@ -209,7 +186,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Bundle.objects.get(pk=bundle_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=bundle_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Bundle,
@@ -236,7 +213,7 @@ def _get_audit_operation_and_object(
                 name=f"{AuditObjectType.Cluster.capitalize()} {operation_type}d",
                 operation_type=operation_type,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -251,7 +228,7 @@ def _get_audit_operation_and_object(
                 audit_operation.name = audit_operation.name.format(host_fqdn=res.data["fqdn"])
 
             obj = Cluster.objects.get(pk=cluster_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -263,7 +240,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Cluster.objects.get(pk=cluster_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -276,7 +253,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Cluster.objects.get(pk=cluster_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -294,7 +271,7 @@ def _get_audit_operation_and_object(
                 )
 
             obj = Cluster.objects.get(pk=cluster_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -311,7 +288,7 @@ def _get_audit_operation_and_object(
                     service_display_name=deleted_obj.display_name
                 )
             obj = Cluster.objects.get(pk=cluster_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -325,7 +302,7 @@ def _get_audit_operation_and_object(
                      f"{cluster.name}/{_get_service_name(service)}",
                 operation_type=AuditLogOperationType.Update,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=service.name,
                 object_type=AuditObjectType.Service,
@@ -338,7 +315,7 @@ def _get_audit_operation_and_object(
                 name=f"{cluster.name}/{_get_service_name(service)} unbound",
                 operation_type=AuditLogOperationType.Update,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=service.name,
                 object_type=AuditObjectType.Service,
@@ -351,7 +328,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ClusterObject.objects.get(pk=service_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -364,7 +341,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ClusterObject.objects.get(pk=service_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -377,7 +354,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ServiceComponent.objects.get(pk=component_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=component_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Component,
@@ -396,7 +373,7 @@ def _get_audit_operation_and_object(
                 audit_operation.name = audit_operation.name.format(
                     service_display_name=_get_service_name(service),
                 )
-                audit_object, _ = AuditObject.objects.get_or_create(
+                audit_object = _get_or_create_audit_obj(
                     object_id=cluster_pk,
                     object_name=obj.name,
                     object_type=AuditObjectType.Cluster,
@@ -417,7 +394,7 @@ def _get_audit_operation_and_object(
                     service_display_name=_get_service_name(deleted_obj),
                 )
 
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -433,7 +410,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Cluster.objects.get(pk=cluster_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=cluster_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Cluster,
@@ -446,7 +423,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Host.objects.get(pk=host_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=host_pk,
                 object_name=obj.fqdn,
                 object_type=AuditObjectType.Host,
@@ -470,7 +447,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ServiceComponent.objects.get(pk=component_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=component_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Component,
@@ -483,7 +460,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ClusterObject.objects.get(pk=service_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -496,7 +473,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Host.objects.get(pk=host_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=host_pk,
                 object_name=obj.fqdn,
                 object_type=AuditObjectType.Host,
@@ -513,7 +490,7 @@ def _get_audit_operation_and_object(
                     res.data.serializer.instance.obj_ref.object
                 ).name
                 object_type = _get_obj_type(object_type)
-                audit_object, _ = AuditObject.objects.get_or_create(
+                audit_object = _get_or_create_audit_obj(
                     object_id=res.data.serializer.instance.id,
                     object_name=str(res.data.serializer.instance),
                     object_type=object_type,
@@ -543,7 +520,7 @@ def _get_audit_operation_and_object(
                     obj = res.data.serializer.instance
 
                 object_type = _get_obj_type(obj.object_type.name)
-                audit_object, _ = AuditObject.objects.get_or_create(
+                audit_object = _get_or_create_audit_obj(
                     object_id=obj.object.id,
                     object_name=obj.object.name,
                     object_type=object_type,
@@ -562,7 +539,7 @@ def _get_audit_operation_and_object(
             if res:
                 object_type = _get_obj_type(config_group.object_type.name)
                 audit_operation.name = audit_operation.name.format(fqdn=res.data["fqdn"])
-                audit_object, _ = AuditObject.objects.get_or_create(
+                audit_object = _get_or_create_audit_obj(
                     object_id=config_group.pk,
                     object_name=config_group.object.name,
                     object_type=object_type,
@@ -580,7 +557,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             object_type = _get_obj_type(config_group.object_type.name)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=config_group.pk,
                 object_name=config_group.object.name,
                 object_type=object_type,
@@ -608,7 +585,7 @@ def _get_audit_operation_and_object(
                      f"{operation_type}d",
                 operation_type=operation_type,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=group_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Group,
@@ -636,7 +613,7 @@ def _get_audit_operation_and_object(
                      f"{operation_type}d",
                 operation_type=operation_type,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=policy_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Policy,
@@ -664,7 +641,7 @@ def _get_audit_operation_and_object(
                      f"{operation_type}d",
                 operation_type=operation_type,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=role_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Role,
@@ -677,7 +654,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Create,
             )
             if res:
-                audit_object, _ = AuditObject.objects.get_or_create(
+                audit_object = _get_or_create_audit_obj(
                     object_id=res.data["id"],
                     object_name=res.data["username"],
                     object_type=AuditObjectType.User,
@@ -699,7 +676,7 @@ def _get_audit_operation_and_object(
                      f"{operation_type}d",
                 operation_type=operation_type,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=user_pk,
                 object_name=obj.username,
                 object_type=AuditObjectType.User,
@@ -712,7 +689,7 @@ def _get_audit_operation_and_object(
                      f"{AuditLogOperationType.Delete}d",
                 operation_type=AuditLogOperationType.Delete,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=host_pk,
                 object_name=deleted_obj.fqdn,
                 object_type=AuditObjectType.Host,
@@ -725,7 +702,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Create,
             )
             if res:
-                audit_object, _ = AuditObject.objects.get_or_create(
+                audit_object = _get_or_create_audit_obj(
                     object_id=res.data["id"],
                     object_name=res.data["fqdn"],
                     object_type=AuditObjectType.Host,
@@ -740,7 +717,7 @@ def _get_audit_operation_and_object(
                      f"configuration {AuditLogOperationType.Update}d",
                 operation_type=AuditLogOperationType.Update,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=obj.pk,
                 object_name=obj.fqdn,
                 object_type=AuditObjectType.Host,
@@ -764,7 +741,7 @@ def _get_audit_operation_and_object(
                      f"{AuditLogOperationType.Delete}d",
                 operation_type=AuditLogOperationType.Delete,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=provider_pk,
                 object_name=deleted_obj.name,
                 object_type=AuditObjectType.Provider,
@@ -777,7 +754,7 @@ def _get_audit_operation_and_object(
                 f"configuration {AuditLogOperationType.Update}d",
                 operation_type=AuditLogOperationType.Update,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                     object_id=provider_pk,
                     object_name=obj.name,
                     object_type=AuditObjectType.Provider,
@@ -793,7 +770,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = Host.objects.get(pk=host_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=host_pk,
                 object_name=obj.fqdn,
                 object_type=AuditObjectType.Host,
@@ -805,7 +782,7 @@ def _get_audit_operation_and_object(
                 name=f"{deleted_obj.display_name} service removed",
                 operation_type=AuditLogOperationType.Update,
             )
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=deleted_obj.cluster.pk,
                 object_name=deleted_obj.cluster.name,
                 object_type=AuditObjectType.Cluster,
@@ -818,7 +795,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ClusterObject.objects.get(pk=service_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -837,7 +814,7 @@ def _get_audit_operation_and_object(
                     export_cluster_name=res.data["export_cluster_name"],
                 )
 
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -856,7 +833,7 @@ def _get_audit_operation_and_object(
                     export_cluster_name=deleted_obj[0].cluster.name,
                 )
 
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -872,7 +849,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ServiceComponent.objects.get(pk=component_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=component_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Component,
@@ -888,7 +865,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ClusterObject.objects.get(pk=service_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=service_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Service,
@@ -901,7 +878,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ServiceComponent.objects.get(pk=component_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=component_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.Component,
@@ -914,7 +891,7 @@ def _get_audit_operation_and_object(
                 operation_type=AuditLogOperationType.Update,
             )
             obj = ADCM.objects.get(pk=adcm_pk)
-            audit_object, _ = AuditObject.objects.get_or_create(
+            audit_object = _get_or_create_audit_obj(
                 object_id=adcm_pk,
                 object_name=obj.name,
                 object_type=AuditObjectType.ADCM,
@@ -1000,10 +977,49 @@ def wrapped(*args, **kwargs):
     return wrapped
 
 
-def mark_deleted_audit_object(instance):
+def mark_deleted_audit_object(instance, object_type: str):
     audit_objs = []
-    for audit_obj in AuditObject.objects.filter(object_id=instance.pk):
+    for audit_obj in AuditObject.objects.filter(object_id=instance.pk, object_type=object_type):
         audit_obj.is_deleted = True
         audit_objs.append(audit_obj)
 
     AuditObject.objects.bulk_update(objs=audit_objs, fields=["is_deleted"])
+
+
+def make_audit_log(operation_type, result, operation_status):
+    operation_type_map = {
+        "task_db": {
+            "type": AuditLogOperationType.Delete,
+            "name": '"Task log cleanup in database on schedule" job',
+        },
+        "task_fs": {
+            "type": AuditLogOperationType.Delete,
+            "name": '"Task log cleanup in filesystem on schedule" job',
+        },
+        "config": {
+            "type": AuditLogOperationType.Delete,
+            "name": '"Objects configurations cleanup on schedule" job',
+        },
+        "sync": {"type": AuditLogOperationType.Update, "name": '"User sync on schedule" job'},
+        "audit": {
+            "type": AuditLogOperationType.Delete,
+            "name": '"Audit log cleanup/archiving on schedule" job',
+        },
+    }
+    result = (
+        AuditLogOperationResult.Success if result == 'success' else AuditLogOperationResult.Fail
+    )
+    operation_name = operation_type_map[operation_type]["name"] + ' ' + operation_status
+    audit_object = _get_or_create_audit_obj(
+        object_id=ADCM.objects.get().id,
+        object_name='ADCM',
+        object_type=AuditObjectType.ADCM,
+    )
+    system_user = User.objects.get(username='system')
+    AuditLog.objects.create(
+        audit_object=audit_object,
+        operation_name=operation_name,
+        operation_type=operation_type_map[operation_type]['type'],
+        operation_result=result,
+        user=system_user,
+    )
diff --git a/python/cm/adcm_config.py b/python/cm/adcm_config.py
index 793d7ff827..659ef4fc1b 100644
--- a/python/cm/adcm_config.py
+++ b/python/cm/adcm_config.py
@@ -17,20 +17,17 @@
 import os
 from collections import OrderedDict
 from collections.abc import Mapping
-from typing import Any, Tuple, Optional
-
-import yspec.checker
-from ansible.parsing.vault import VaultSecret, VaultAES256
-from django.conf import settings
-from django.db.utils import OperationalError
+from typing import Any, Optional, Tuple
 
 import cm.variant
+import yspec.checker
+from ansible.parsing.vault import VaultAES256, VaultSecret
 from cm import config
 from cm.errors import raise_AdcmEx as err
 from cm.logger import log
 from cm.models import (
-    Action,
     ADCM,
+    Action,
     ADCMEntity,
     ConfigLog,
     GroupConfig,
@@ -38,7 +35,8 @@
     Prototype,
     PrototypeConfig,
 )
-
+from django.conf import settings
+from django.db.utils import OperationalError
 
 SECURE_PARAM_TYPES = ('password', 'secrettext')
 
diff --git a/python/cm/ansible_plugin.py b/python/cm/ansible_plugin.py
index 78bf04d362..f57f726140 100644
--- a/python/cm/ansible_plugin.py
+++ b/python/cm/ansible_plugin.py
@@ -20,28 +20,27 @@
 from ansible.errors import AnsibleError
 from ansible.plugins.action import ActionBase
 from ansible.utils.vars import merge_hash
-
-import cm
+from cm import config
+from cm.adcm_config import set_object_config
 from cm.api import add_hc, get_hc
 from cm.api_context import ctx
-from cm.adcm_config import set_object_config
-from cm import config
+from cm.errors import AdcmEx
 from cm.errors import raise_AdcmEx as err
-from cm.status_api import post_event
 from cm.models import (
+    Action,
     ADCMEntity,
     CheckLog,
     Cluster,
     ClusterObject,
     GroupCheckLog,
-    LogStorage,
-    ServiceComponent,
-    HostProvider,
     Host,
-    Prototype,
-    Action,
+    HostProvider,
     JobLog,
+    LogStorage,
+    Prototype,
+    ServiceComponent,
 )
+from cm.status_api import post_event
 
 MSG_NO_CONFIG = (
     "There are no job related vars in inventory. It's mandatory for that module to have some"
@@ -129,7 +128,7 @@ class ContextActionModule(ActionBase):
     def _wrap_call(self, func, *args):
         try:
             func(*args)
-        except cm.errors.AdcmEx as e:
+        except AdcmEx as e:
             return {'failed': True, 'msg': e.msg}
         return {'changed': True}
 
diff --git a/python/cm/api.py b/python/cm/api.py
index 8e3f7ed933..9409a34e71 100644
--- a/python/cm/api.py
+++ b/python/cm/api.py
@@ -13,11 +13,6 @@
 
 import json
 
-from django.core.exceptions import MultipleObjectsReturned
-from django.db import transaction
-from django.utils import timezone
-from version_utils import rpm
-
 import cm.issue
 import cm.status_api
 from cm.adcm_config import (
@@ -30,11 +25,13 @@
     save_obj_config,
 )
 from cm.api_context import ctx
-from cm.errors import AdcmEx, raise_AdcmEx as err
+from cm.errors import AdcmEx
+from cm.errors import raise_AdcmEx as err
 from cm.logger import log
 from cm.models import (
     ADCM,
     ADCMEntity,
+    Bundle,
     Cluster,
     ClusterBind,
     ClusterObject,
@@ -45,15 +42,18 @@
     Host,
     HostComponent,
     HostProvider,
+    MaintenanceModeType,
     Prototype,
     PrototypeExport,
     PrototypeImport,
     ServiceComponent,
     TaskLog,
-    Bundle,
-    MaintenanceModeType,
 )
+from django.core.exceptions import MultipleObjectsReturned
+from django.db import transaction
+from django.utils import timezone
 from rbac.models import re_apply_object_policy
+from version_utils import rpm
 
 
 def check_license(bundle: Bundle) -> None:
diff --git a/python/cm/bundle.py b/python/cm/bundle.py
index 1bf87255e1..0d333c96e4 100644
--- a/python/cm/bundle.py
+++ b/python/cm/bundle.py
@@ -17,14 +17,10 @@
 import shutil
 import tarfile
 
-from django.db import transaction, IntegrityError
-from version_utils import rpm
-
 import cm.stack
 import cm.status_api
-from adcm.settings import ADCM_VERSION
 from cm import config
-from cm.adcm_config import proto_ref, init_object_config, switch_config
+from cm.adcm_config import init_object_config, proto_ref, switch_config
 from cm.errors import raise_AdcmEx as err
 from cm.logger import log
 from cm.models import (
@@ -48,8 +44,12 @@
     SubAction,
     Upgrade,
 )
+from django.db import IntegrityError, transaction
 from rbac.models import Role
 from rbac.upgrade.role import prepare_action_roles
+from version_utils import rpm
+
+from adcm.settings import ADCM_VERSION
 
 STAGE = (
     StagePrototype,
diff --git a/python/cm/daemon.py b/python/cm/daemon.py
index 9f7719de89..c3b0dc35b3 100644
--- a/python/cm/daemon.py
+++ b/python/cm/daemon.py
@@ -10,10 +10,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import atexit
 import os
 import sys
 import time
-import atexit
 from signal import SIGTERM
 
 
diff --git a/python/cm/errors.py b/python/cm/errors.py
index 94f3bfc320..9b700160e8 100644
--- a/python/cm/errors.py
+++ b/python/cm/errors.py
@@ -11,12 +11,10 @@
 # limitations under the License.
 
 import rest_framework.status as rfs
+from cm.logger import log
 from rest_framework.exceptions import APIException
 from rest_framework.views import exception_handler
 
-from cm.logger import log
-
-
 WARN = 'warning'
 ERR = 'error'
 CRIT = 'critical'
diff --git a/python/cm/hierarchy.py b/python/cm/hierarchy.py
index 0bba218ece..7b67e49e4a 100644
--- a/python/cm/hierarchy.py
+++ b/python/cm/hierarchy.py
@@ -10,15 +10,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import Set, Optional, Tuple
+from typing import Optional, Set, Tuple
 
 from cm.models import (
     ADCMEntity,
     ClusterObject,
     Host,
     HostComponent,
-    ServiceComponent,
     MaintenanceModeType,
+    ServiceComponent,
 )
 
 
diff --git a/python/cm/inventory.py b/python/cm/inventory.py
index 542852a068..b88f874130 100644
--- a/python/cm/inventory.py
+++ b/python/cm/inventory.py
@@ -14,27 +14,26 @@
 import os
 from itertools import chain
 
-from django.contrib.contenttypes.models import ContentType
-
 from cm import config
 from cm.adcm_config import get_prototype_config, process_config
 from cm.logger import log
 from cm.models import (
     Cluster,
+    ClusterBind,
     ClusterObject,
-    ServiceComponent,
-    HostComponent,
-    Host,
     ConfigLog,
-    ClusterBind,
-    PrototypeExport,
+    GroupConfig,
+    Host,
+    HostComponent,
     HostProvider,
+    MaintenanceModeType,
     Prototype,
+    PrototypeExport,
     PrototypeImport,
+    ServiceComponent,
     get_object_cluster,
-    GroupConfig,
-    MaintenanceModeType,
 )
+from django.contrib.contenttypes.models import ContentType
 
 
 def process_config_and_attr(obj, conf, attr=None, spec=None):
diff --git a/python/cm/issue.py b/python/cm/issue.py
index 6a6ab21191..49322f2425 100644
--- a/python/cm/issue.py
+++ b/python/cm/issue.py
@@ -10,8 +10,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from cm.adcm_config import proto_ref, obj_ref, get_prototype_config
-from cm.errors import AdcmEx, raise_AdcmEx as err
+from cm.adcm_config import get_prototype_config, obj_ref, proto_ref
+from cm.errors import AdcmEx
+from cm.errors import raise_AdcmEx as err
 from cm.hierarchy import Tree
 from cm.logger import log
 from cm.models import (
@@ -25,11 +26,9 @@
     ConfigLog,
     Host,
     HostComponent,
-    MessageTemplate as MsgTpl,
-    PrototypeEnum,
-    Prototype,
-    PrototypeImport,
 )
+from cm.models import MessageTemplate as MsgTpl
+from cm.models import Prototype, PrototypeEnum, PrototypeImport
 
 
 def check_config(obj):  # pylint: disable=too-many-branches
diff --git a/python/cm/job.py b/python/cm/job.py
index 439711f31f..88ce750aad 100644
--- a/python/cm/job.py
+++ b/python/cm/job.py
@@ -16,12 +16,9 @@
 import os
 import subprocess
 from configparser import ConfigParser
-from typing import List, Tuple, Optional, Hashable, Any, Union
+from typing import Any, Hashable, List, Optional, Tuple, Union
 
-from django.db import transaction
-from django.utils import timezone
-
-from cm import api, inventory, adcm_config, variant, config, issue
+from cm import adcm_config, api, config, inventory, issue, variant
 from cm.adcm_config import process_file_type
 from cm.api_context import ctx
 from cm.errors import raise_AdcmEx as err
@@ -30,9 +27,9 @@
 from cm.logger import log
 from cm.models import (
     ADCM,
-    ADCMEntity,
     Action,
     ActionType,
+    ADCMEntity,
     Cluster,
     ClusterObject,
     ConfigLog,
@@ -48,6 +45,8 @@
     get_object_cluster,
 )
 from cm.status_api import post_event
+from django.db import transaction
+from django.utils import timezone
 from rbac.roles import re_apply_policy_for_jobs
 
 
diff --git a/python/cm/management/commands/dumpcluster.py b/python/cm/management/commands/dumpcluster.py
index 8c62d9f01b..e10ddef7d1 100644
--- a/python/cm/management/commands/dumpcluster.py
+++ b/python/cm/management/commands/dumpcluster.py
@@ -12,32 +12,31 @@
 
 # pylint: disable=too-many-locals
 
-import json
-import sys
 import base64
 import getpass
-from cryptography.fernet import Fernet
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
-
-from django.conf import settings
-from django.core.management.base import BaseCommand
+import json
+import sys
 
 from cm.config import ANSIBLE_SECRET, DEFAULT_SALT
 from cm.models import (
     Bundle,
     Cluster,
-    Prototype,
     ClusterObject,
-    ServiceComponent,
+    ConfigLog,
+    GroupConfig,
     Host,
+    HostComponent,
     HostProvider,
-    GroupConfig,
     ObjectConfig,
-    ConfigLog,
-    HostComponent,
+    Prototype,
+    ServiceComponent,
 )
+from cryptography.fernet import Fernet
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from django.conf import settings
+from django.core.management.base import BaseCommand
 
 
 def serialize_datetime_fields(obj, fields=None):
diff --git a/python/cm/management/commands/loadcluster.py b/python/cm/management/commands/loadcluster.py
index b6ccc910fa..2d68c97213 100644
--- a/python/cm/management/commands/loadcluster.py
+++ b/python/cm/management/commands/loadcluster.py
@@ -12,40 +12,39 @@
 
 # pylint: disable=too-many-locals, global-statement
 
-import json
 import base64
 import getpass
+import json
 import sys
 from datetime import datetime
-from cryptography.fernet import Fernet, InvalidToken
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
-from ansible.parsing.vault import VaultSecret, VaultAES256
 
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from django.contrib.contenttypes.models import ContentType
-from django.db.transaction import atomic
-from django.db.utils import IntegrityError
-
-from cm.config import ANSIBLE_SECRET, DEFAULT_SALT, ANSIBLE_VAULT_HEADER
-from cm.errors import AdcmEx
+from ansible.parsing.vault import VaultAES256, VaultSecret
 from cm.adcm_config import save_file_type
+from cm.config import ANSIBLE_SECRET, ANSIBLE_VAULT_HEADER, DEFAULT_SALT
+from cm.errors import AdcmEx
 from cm.models import (
     Bundle,
     Cluster,
-    Prototype,
     ClusterObject,
-    ServiceComponent,
+    ConfigLog,
+    GroupConfig,
     Host,
+    HostComponent,
     HostProvider,
-    GroupConfig,
     ObjectConfig,
-    ConfigLog,
+    Prototype,
     PrototypeConfig,
-    HostComponent,
+    ServiceComponent,
 )
+from cryptography.fernet import Fernet, InvalidToken
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.core.management.base import BaseCommand
+from django.db.transaction import atomic
+from django.db.utils import IntegrityError
 
 OLD_ADCM_PASSWORD = None
 
diff --git a/python/cm/management/commands/logrotate.py b/python/cm/management/commands/logrotate.py
index 6f76e78784..0e2e7e4593 100644
--- a/python/cm/management/commands/logrotate.py
+++ b/python/cm/management/commands/logrotate.py
@@ -13,15 +13,10 @@
 
 import os
 import shutil
-
+from datetime import datetime, timedelta
 from enum import Enum
+from subprocess import STDOUT, CalledProcessError, check_output
 
-from datetime import timedelta, datetime
-from subprocess import check_output, CalledProcessError, STDOUT
-
-from django.core.management.base import BaseCommand
-from django.db import transaction
-from django.utils import timezone
 from audit.utils import make_audit_log
 from cm import config
 from cm.logger import log_cron_task as log
@@ -39,6 +34,9 @@
     ServiceComponent,
     TaskLog,
 )
+from django.core.management.base import BaseCommand
+from django.db import transaction
+from django.utils import timezone
 
 LOGROTATE_CONF_FILE_TEMPLATE = """
 /adcm/data/log/nginx/*.log {{
diff --git a/python/cm/management/commands/run_ldap_sync.py b/python/cm/management/commands/run_ldap_sync.py
index 481f4fd103..6918c7363c 100644
--- a/python/cm/management/commands/run_ldap_sync.py
+++ b/python/cm/management/commands/run_ldap_sync.py
@@ -12,14 +12,13 @@
 
 from datetime import timedelta
 
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-
 from audit.utils import make_audit_log
 from cm.config import Job
 from cm.job import start_task
-from cm.models import ADCM, Action, ConfigLog, TaskLog
 from cm.logger import log_cron_task as log
+from cm.models import ADCM, Action, ConfigLog, TaskLog
+from django.core.management.base import BaseCommand
+from django.utils import timezone
 
 
 def get_settings(adcm_object):
diff --git a/python/cm/migrations/0001_initial.py b/python/cm/migrations/0001_initial.py
index a3efdb12ab..d292876baf 100644
--- a/python/cm/migrations/0001_initial.py
+++ b/python/cm/migrations/0001_initial.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2018-06-08 14:24
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0002_auto_20180801_1117.py b/python/cm/migrations/0002_auto_20180801_1117.py
index e185b049e1..48edc337f7 100644
--- a/python/cm/migrations/0002_auto_20180801_1117.py
+++ b/python/cm/migrations/0002_auto_20180801_1117.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2018-08-01 11:17
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0004_auto_20180914_1042.py b/python/cm/migrations/0004_auto_20180914_1042.py
index 4f69f7ab25..7070aedd09 100644
--- a/python/cm/migrations/0004_auto_20180914_1042.py
+++ b/python/cm/migrations/0004_auto_20180914_1042.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2018-09-14 10:42
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0005_auto_20180928_0945.py b/python/cm/migrations/0005_auto_20180928_0945.py
index 86d0f90b50..3997a8229d 100644
--- a/python/cm/migrations/0005_auto_20180928_0945.py
+++ b/python/cm/migrations/0005_auto_20180928_0945.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2018-09-28 09:45
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0009_auto_20181113_1112.py b/python/cm/migrations/0009_auto_20181113_1112.py
index d9b3ca3568..6ae87bccb7 100644
--- a/python/cm/migrations/0009_auto_20181113_1112.py
+++ b/python/cm/migrations/0009_auto_20181113_1112.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2018-11-13 11:12
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0012_auto_20181226_0926.py b/python/cm/migrations/0012_auto_20181226_0926.py
index 881aaece96..60e03fd15c 100644
--- a/python/cm/migrations/0012_auto_20181226_0926.py
+++ b/python/cm/migrations/0012_auto_20181226_0926.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2018-12-26 09:26
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0013_auto_20190116_1143.py b/python/cm/migrations/0013_auto_20190116_1143.py
index 08b7666822..5e46423e1c 100644
--- a/python/cm/migrations/0013_auto_20190116_1143.py
+++ b/python/cm/migrations/0013_auto_20190116_1143.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2019-01-16 11:43
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0014_auto_20190124_1344.py b/python/cm/migrations/0014_auto_20190124_1344.py
index b7dd858f1b..fd68e3eb96 100644
--- a/python/cm/migrations/0014_auto_20190124_1344.py
+++ b/python/cm/migrations/0014_auto_20190124_1344.py
@@ -13,6 +13,7 @@
 # pylint: disable=line-too-long
 
 import datetime
+
 from django.db import migrations, models
 from django.utils.timezone import utc
 
diff --git a/python/cm/migrations/0018_auto_20190220_1101.py b/python/cm/migrations/0018_auto_20190220_1101.py
index 9f023b6b29..5872215dd6 100644
--- a/python/cm/migrations/0018_auto_20190220_1101.py
+++ b/python/cm/migrations/0018_auto_20190220_1101.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.0.5 on 2019-02-19 10:51
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0021_auto_20190607_1027.py b/python/cm/migrations/0021_auto_20190607_1027.py
index 832a4125ac..e0758d0541 100644
--- a/python/cm/migrations/0021_auto_20190607_1027.py
+++ b/python/cm/migrations/0021_auto_20190607_1027.py
@@ -12,8 +12,8 @@
 # Generated by Django 2.1.7 on 2019-06-07 10:27
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0022_auto_20190620_1251.py b/python/cm/migrations/0022_auto_20190620_1251.py
index 302cbf3105..fd14853a42 100644
--- a/python/cm/migrations/0022_auto_20190620_1251.py
+++ b/python/cm/migrations/0022_auto_20190620_1251.py
@@ -13,8 +13,8 @@
 # pylint: disable=line-too-long
 
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0032_auto_20191015_1600.py b/python/cm/migrations/0032_auto_20191015_1600.py
index 84cc39f7a4..4580b2f0d9 100644
--- a/python/cm/migrations/0032_auto_20191015_1600.py
+++ b/python/cm/migrations/0032_auto_20191015_1600.py
@@ -13,8 +13,8 @@
 # pylint: disable=line-too-long
 
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0037_auto_20191120_1600.py b/python/cm/migrations/0037_auto_20191120_1600.py
index ee64c36086..8af910dcce 100644
--- a/python/cm/migrations/0037_auto_20191120_1600.py
+++ b/python/cm/migrations/0037_auto_20191120_1600.py
@@ -11,8 +11,8 @@
 # limitations under the License.
 # Generated by Django 2.2.1 on 2019-11-20 16:00
 
-from django.db import migrations
 from cm.logger import log
+from django.db import migrations
 
 
 def fix_task(apps, schema_editor):
diff --git a/python/cm/migrations/0048_auto_20200228_1032.py b/python/cm/migrations/0048_auto_20200228_1032.py
index dd7d8a14bd..dc9f9da71d 100644
--- a/python/cm/migrations/0048_auto_20200228_1032.py
+++ b/python/cm/migrations/0048_auto_20200228_1032.py
@@ -15,10 +15,9 @@
 import json
 import os
 
-from django.db import migrations, models
 import django.db.models.deletion
-
 from cm import config
+from django.db import migrations, models
 
 
 def get_body(job, name, type_log, format_log):
diff --git a/python/cm/migrations/0051_auto_20200403_0825.py b/python/cm/migrations/0051_auto_20200403_0825.py
index 5b76e73d5d..7d6bc0331e 100644
--- a/python/cm/migrations/0051_auto_20200403_0825.py
+++ b/python/cm/migrations/0051_auto_20200403_0825.py
@@ -14,8 +14,8 @@
 
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0057_auto_20200831_1055.py b/python/cm/migrations/0057_auto_20200831_1055.py
index 60ffbb9509..8c9830c4a2 100644
--- a/python/cm/migrations/0057_auto_20200831_1055.py
+++ b/python/cm/migrations/0057_auto_20200831_1055.py
@@ -12,9 +12,8 @@
 #
 # Generated by Django 3.1 on 2020-08-31 10:55
 
-from django.db import migrations, models
-
 import cm.models
+from django.db import migrations, models
 
 
 def fix_default_json_fields_component(apps, schema_editor):
diff --git a/python/cm/migrations/0058_encrypt_passwords.py b/python/cm/migrations/0058_encrypt_passwords.py
index b6cdfae469..c7ae7eeaa8 100644
--- a/python/cm/migrations/0058_encrypt_passwords.py
+++ b/python/cm/migrations/0058_encrypt_passwords.py
@@ -11,8 +11,9 @@
 # limitations under the License.
 
 import json
-from django.db import migrations
+
 from cm.adcm_config import ansible_encrypt_and_format, obj_to_dict
+from django.db import migrations
 
 
 def get_prototype_config(proto, PrototypeConfig):
diff --git a/python/cm/migrations/0060_auto_20201201_1122.py b/python/cm/migrations/0060_auto_20201201_1122.py
index 38ffcce1d1..a2f37cad2e 100644
--- a/python/cm/migrations/0060_auto_20201201_1122.py
+++ b/python/cm/migrations/0060_auto_20201201_1122.py
@@ -13,11 +13,10 @@
 # Generated by Django 3.1.2 on 2020-12-01 11:22
 # pylint: disable=line-too-long
 
+import cm.models
 import django.db.models.deletion
 from django.db import migrations, models
 
-import cm.models
-
 
 def create_component_prototype(apps, schema_editor):
     Prototype = apps.get_model('cm', 'Prototype')
diff --git a/python/cm/migrations/0066_auto_20210427_0853.py b/python/cm/migrations/0066_auto_20210427_0853.py
index e432a9d14f..b62660d195 100644
--- a/python/cm/migrations/0066_auto_20210427_0853.py
+++ b/python/cm/migrations/0066_auto_20210427_0853.py
@@ -12,8 +12,8 @@
 # Generated by Django 3.1.2 on 2021-04-27 08:53
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 def fix_tasklog(apps, schema_editor):
diff --git a/python/cm/migrations/0067_tasklog_object_type.py b/python/cm/migrations/0067_tasklog_object_type.py
index 9e765846c7..bae6efe6c6 100644
--- a/python/cm/migrations/0067_tasklog_object_type.py
+++ b/python/cm/migrations/0067_tasklog_object_type.py
@@ -11,8 +11,8 @@
 # limitations under the License.
 # Generated by Django 3.1.2 on 2021-06-04 15:37
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 def fix_tasklog(apps, schema_editor):
diff --git a/python/cm/migrations/0068_alter_tasklog_action.py b/python/cm/migrations/0068_alter_tasklog_action.py
index 20fc6269a6..b24b065fdd 100644
--- a/python/cm/migrations/0068_alter_tasklog_action.py
+++ b/python/cm/migrations/0068_alter_tasklog_action.py
@@ -12,8 +12,8 @@
 # Generated by Django 3.2.4 on 2021-08-05 14:31
 # pylint: disable=line-too-long
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 def fix_joblog(apps, schema_editor):
diff --git a/python/cm/migrations/0070_lock_refactoring.py b/python/cm/migrations/0070_lock_refactoring.py
index bea7b28014..ac6233e082 100644
--- a/python/cm/migrations/0070_lock_refactoring.py
+++ b/python/cm/migrations/0070_lock_refactoring.py
@@ -11,8 +11,8 @@
 # limitations under the License.
 # Generated by Django 3.2 on 2021-06-22 09:44
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0073_issues_refactoring.py b/python/cm/migrations/0073_issues_refactoring.py
index 4f82f5c63d..a36b77baeb 100644
--- a/python/cm/migrations/0073_issues_refactoring.py
+++ b/python/cm/migrations/0073_issues_refactoring.py
@@ -13,7 +13,6 @@
 
 from django.db import migrations, models
 
-
 data = [
     {
         'name': 'object config issue',
diff --git a/python/cm/migrations/0078_alter_groupconfig_name.py b/python/cm/migrations/0078_alter_groupconfig_name.py
index 939a37c18f..55346e819f 100644
--- a/python/cm/migrations/0078_alter_groupconfig_name.py
+++ b/python/cm/migrations/0078_alter_groupconfig_name.py
@@ -12,9 +12,8 @@
 
 # Generated by Django 3.2.6 on 2021-10-25 07:31
 
-from django.db import migrations, models
-
 import cm.models
+from django.db import migrations, models
 
 
 def remove_line_break_character(apps, schema_editor):
diff --git a/python/cm/migrations/0081_auto_20220114_1246.py b/python/cm/migrations/0081_auto_20220114_1246.py
index dfa9507999..9e4f71805e 100644
--- a/python/cm/migrations/0081_auto_20220114_1246.py
+++ b/python/cm/migrations/0081_auto_20220114_1246.py
@@ -12,9 +12,8 @@
 
 # Generated by Django 3.2.9 on 2022-01-14 12:46
 
-from django.db import migrations, models
-
 import cm.models
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/migrations/0083_add_product_category.py b/python/cm/migrations/0083_add_product_category.py
index 78206ff9d0..593d1f6e05 100644
--- a/python/cm/migrations/0083_add_product_category.py
+++ b/python/cm/migrations/0083_add_product_category.py
@@ -12,8 +12,8 @@
 
 # Generated by Django 3.2 on 2021-12-16 13:57
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 def fill_category(apps, schema_editor):
diff --git a/python/cm/migrations/0085_add_action_to_upgrade.py b/python/cm/migrations/0085_add_action_to_upgrade.py
index 5a6a2c3d1c..ec580e06c1 100644
--- a/python/cm/migrations/0085_add_action_to_upgrade.py
+++ b/python/cm/migrations/0085_add_action_to_upgrade.py
@@ -1,7 +1,7 @@
 # Generated by Django 3.2.4 on 2022-03-23 11:06
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
diff --git a/python/cm/signals.py b/python/cm/signals.py
index eb1a3a20fb..b422f91833 100644
--- a/python/cm/signals.py
+++ b/python/cm/signals.py
@@ -10,30 +10,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from audit.models import AUDIT_OBJECT_TYPE_TO_MODEL_MAP
 from audit.utils import mark_deleted_audit_object
 from cm.models import (
+    ADCM,
     Bundle,
     Cluster,
     ClusterObject,
-    ConfigLog,
-    GroupConfig,
     Host,
     HostProvider,
-    ADCM,
     ServiceComponent,
 )
 from django.db.models.signals import post_delete
 from django.dispatch import receiver
 
 
-@receiver(post_delete, sender=ADCM)
-@receiver(post_delete, sender=Bundle)
 @receiver(post_delete, sender=Cluster)
 @receiver(post_delete, sender=ClusterObject)
-@receiver(post_delete, sender=ConfigLog)
-@receiver(post_delete, sender=GroupConfig)
+@receiver(post_delete, sender=ServiceComponent)
 @receiver(post_delete, sender=Host)
 @receiver(post_delete, sender=HostProvider)
-@receiver(post_delete, sender=ServiceComponent)
+@receiver(post_delete, sender=Bundle)
+@receiver(post_delete, sender=ADCM)
 def mark_deleted_audit_object_handler(sender, instance, **kwargs):
-    mark_deleted_audit_object(instance)
+    mark_deleted_audit_object(instance=instance, object_type=AUDIT_OBJECT_TYPE_TO_MODEL_MAP[sender])
diff --git a/python/cm/stack.py b/python/cm/stack.py
index a98204bb69..571fb44429 100644
--- a/python/cm/stack.py
+++ b/python/cm/stack.py
@@ -11,31 +11,39 @@
 # limitations under the License.
 # pylint: disable=line-too-long,too-many-statements
 
+import hashlib
+import json
 import os
 import re
+import warnings
 from copy import deepcopy
 from typing import Any
-from version_utils import rpm
 
-import json
-import yaml
+import cm.checker
 import ruyaml
-import hashlib
-import warnings
+import yaml
 import yspec.checker
+from cm import config
+from cm.adcm_config import (
+    check_config_type,
+    proto_ref,
+    read_bundle_file,
+    type_is_complex,
+)
+from cm.errors import raise_AdcmEx as err
+from cm.logger import log
+from cm.models import (
+    StageAction,
+    StagePrototype,
+    StagePrototypeConfig,
+    StagePrototypeExport,
+    StagePrototypeImport,
+    StageSubAction,
+    StageUpgrade,
+)
 from django.db import IntegrityError
-
 from rest_framework import status
-
-from cm.logger import log
-from cm.errors import raise_AdcmEx as err
-from cm import config
-import cm.checker
-
-from cm.adcm_config import proto_ref, check_config_type, type_is_complex, read_bundle_file
-from cm.models import StagePrototype, StageAction, StagePrototypeConfig
-from cm.models import StagePrototypeExport, StagePrototypeImport, StageUpgrade, StageSubAction
-
+from version_utils import rpm
 
 NAME_REGEX = r'[0-9a-zA-Z_\.-]+'
 MAX_NAME_LENGTH = 256
diff --git a/python/cm/status_api.py b/python/cm/status_api.py
index c500d56ccb..9b7927d4e9 100644
--- a/python/cm/status_api.py
+++ b/python/cm/status_api.py
@@ -15,16 +15,15 @@
 from typing import Iterable
 
 import requests
-
 from cm.config import STATUS_SECRET_KEY
 from cm.logger import log
 from cm.models import (
     ADCMEntity,
-    ServiceComponent,
-    HostComponent,
-    ClusterObject,
     Cluster,
+    ClusterObject,
     Host,
+    HostComponent,
+    ServiceComponent,
 )
 
 API_URL = "http://localhost:8020/api/v1"
diff --git a/python/cm/upgrade.py b/python/cm/upgrade.py
index 41a5b5cb46..efc714679c 100644
--- a/python/cm/upgrade.py
+++ b/python/cm/upgrade.py
@@ -11,15 +11,12 @@
 # limitations under the License.
 
 import functools
-from typing import Union, Tuple, List
-
-from django.db import transaction
-from version_utils import rpm
+from typing import List, Tuple, Union
 
 import cm.issue
 import cm.job
 import cm.status_api
-from cm.adcm_config import proto_ref, obj_ref, switch_config, make_object_config
+from cm.adcm_config import make_object_config, obj_ref, proto_ref, switch_config
 from cm.api import check_license, version_in
 from cm.errors import raise_AdcmEx as err
 from cm.logger import log
@@ -30,12 +27,14 @@
     Host,
     HostComponent,
     HostProvider,
+    MaintenanceModeType,
     Prototype,
     PrototypeImport,
     ServiceComponent,
     Upgrade,
-    MaintenanceModeType,
 )
+from django.db import transaction
+from version_utils import rpm
 
 
 def switch_object(obj: Union[Host, ClusterObject], new_prototype: Prototype) -> None:
diff --git a/python/cm/variant.py b/python/cm/variant.py
index 920d0613c0..101df0bfe6 100644
--- a/python/cm/variant.py
+++ b/python/cm/variant.py
@@ -10,10 +10,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from cm.logger import log
 from cm.errors import AdcmEx
 from cm.errors import raise_AdcmEx as err
-from cm.models import Prototype, ClusterObject, ServiceComponent, HostComponent, Host, GroupConfig
+from cm.logger import log
+from cm.models import (
+    ClusterObject,
+    GroupConfig,
+    Host,
+    HostComponent,
+    Prototype,
+    ServiceComponent,
+)
 
 
 def get_cluster(obj):
diff --git a/python/cm/views.py b/python/cm/views.py
index 4d3d341616..135f1406a6 100644
--- a/python/cm/views.py
+++ b/python/cm/views.py
@@ -12,16 +12,15 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
-from django.shortcuts import render, redirect
-from django.utils.http import urlencode
-from django.contrib.auth import login
 import social_django.views
-from social_core.exceptions import AuthForbidden
-from rest_framework.authtoken.models import Token
-
 from cm import config
 from cm.logger import log
 from cm.models import UserProfile
+from django.contrib.auth import login
+from django.shortcuts import redirect, render
+from django.utils.http import urlencode
+from rest_framework.authtoken.models import Token
+from social_core.exceptions import AuthForbidden
 
 
 def index(request):
diff --git a/python/rbac/signals.py b/python/rbac/signals.py
index b9b6e0092a..26f4d46494 100644
--- a/python/rbac/signals.py
+++ b/python/rbac/signals.py
@@ -10,6 +10,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from audit.models import AUDIT_OBJECT_TYPE_TO_MODEL_MAP
 from audit.utils import mark_deleted_audit_object
 from django.db.models.signals import post_delete
 from django.dispatch import receiver
@@ -23,4 +24,4 @@
 @receiver(post_delete, sender=Role)
 @receiver(post_delete, sender=Token)
 def mark_deleted_audit_object_handler(sender, instance, **kwargs):
-    mark_deleted_audit_object(instance)
+    mark_deleted_audit_object(instance, object_type=AUDIT_OBJECT_TYPE_TO_MODEL_MAP[sender])


Test patch:
diff --git a/python/audit/tests/files/test_cluster_bundle.tar b/python/audit/tests/files/test_cluster_bundle.tar
new file mode 100644
index 0000000000..14615b6aa7
Binary files /dev/null and b/python/audit/tests/files/test_cluster_bundle.tar differ
diff --git a/python/audit/tests/files/test_provider_bundle.tar b/python/audit/tests/files/test_provider_bundle.tar
new file mode 100644
index 0000000000..c4fc248074
Binary files /dev/null and b/python/audit/tests/files/test_provider_bundle.tar differ
diff --git a/python/audit/tests/test_cluster.py b/python/audit/tests/test_cluster.py
index 487809eb9b..f7664ae8df 100644
--- a/python/audit/tests/test_cluster.py
+++ b/python/audit/tests/test_cluster.py
@@ -11,11 +11,13 @@
 # limitations under the License.
 
 from datetime import datetime
+from pathlib import Path
 
 from audit.models import (
     AuditLog,
     AuditLogOperationResult,
     AuditLogOperationType,
+    AuditObject,
     AuditObjectType,
 )
 from cm.models import (
@@ -33,6 +35,7 @@
     PrototypeImport,
     ServiceComponent,
 )
+from django.conf import settings
 from django.urls import reverse
 from rest_framework.response import Response
 
@@ -111,19 +114,23 @@ def check_cluster_update_config(self, log: AuditLog) -> None:
             operation_type=AuditLogOperationType.Update,
         )
 
-    def create_cluster(self):
+    def create_cluster(self, bundle_id: int, name: str, prototype_id: int):
         return self.client.post(
             path=reverse("cluster"),
             data={
-                "bundle_id": self.bundle.pk,
-                "display_name": f"{self.test_cluster_name}_display",
-                "name": self.test_cluster_name,
-                "prototype_id": self.cluster_prototype.pk,
+                "bundle_id": bundle_id,
+                "display_name": f"{name}_display",
+                "name": name,
+                "prototype_id": prototype_id,
             },
         )
 
     def test_create(self):
-        res: Response = self.create_cluster()
+        res: Response = self.create_cluster(
+            bundle_id=self.bundle.pk,
+            name=self.test_cluster_name,
+            prototype_id=self.cluster_prototype.pk,
+        )
 
         log: AuditLog = AuditLog.objects.order_by("operation_time").last()
 
@@ -135,7 +142,11 @@ def test_create(self):
             operation_type=AuditLogOperationType.Create,
         )
 
-        self.create_cluster()
+        self.create_cluster(
+            bundle_id=self.bundle.pk,
+            name=self.test_cluster_name,
+            prototype_id=self.cluster_prototype.pk,
+        )
 
         log: AuditLog = AuditLog.objects.order_by("operation_time").last()
 
@@ -147,6 +158,115 @@ def test_create(self):
         assert log.user.pk == self.test_user.pk
         assert isinstance(log.object_changes, dict)
 
+    def test_delete_two_clusters(self):
+        cluster_bundle_filename = "test_cluster_bundle.tar"
+        provider_bundle_filename = "test_provider_bundle.tar"
+
+        with open(
+            Path(settings.BASE_DIR, "python/audit/tests/files", cluster_bundle_filename),
+            encoding="utf-8",
+        ) as f:
+            self.client.post(
+                path=reverse("upload-bundle"),
+                data={"file": f},
+            )
+
+        cluster_bundle_res: Response = self.client.post(
+            path=reverse("load-bundle"),
+            data={"bundle_file": cluster_bundle_filename},
+        )
+
+        with open(
+            Path(settings.BASE_DIR, "python/audit/tests/files", provider_bundle_filename),
+            encoding="utf-8",
+        ) as f:
+            self.client.post(
+                path=reverse("upload-bundle"),
+                data={"file": f},
+            )
+
+        provider_bundle_res: Response = self.client.post(
+            path=reverse("load-bundle"),
+            data={"bundle_file": provider_bundle_filename},
+        )
+
+        cluster_prototype = Prototype.objects.create(
+            bundle_id=cluster_bundle_res.data["id"], type="cluster"
+        )
+        cluster_1_res: Response = self.create_cluster(
+            bundle_id=cluster_bundle_res.data["id"],
+            name="new_test_cluster_1",
+            prototype_id=cluster_prototype.pk,
+        )
+        self.create_cluster(
+            bundle_id=cluster_bundle_res.data["id"],
+            name="new_test_cluster_2",
+            prototype_id=cluster_prototype.pk,
+        )
+
+        provider_prototype = Prototype.objects.create(
+            bundle_id=provider_bundle_res.data["id"], type="provider"
+        )
+        provider_res: Response = self.client.post(
+            path=reverse("provider"),
+            data={
+                "name": "new_test_provider",
+                "prototype_id": provider_prototype.pk,
+            },
+        )
+
+        host_prototype = Prototype.objects.create(
+            bundle_id=provider_bundle_res.data["id"], type="host"
+        )
+        host_1_res: Response = self.client.post(
+            path=reverse("host"),
+            data={
+                "prototype_id": host_prototype.pk,
+                "provider_id": provider_res.data["id"],
+                "fqdn": "test_fqdn_1",
+            },
+        )
+        self.client.post(
+            path=reverse("host"),
+            data={
+                "prototype_id": host_prototype.pk,
+                "provider_id": provider_res.data["id"],
+                "fqdn": "test_fqdn_2",
+            },
+        )
+
+        self.client.post(
+            path=reverse("host", kwargs={"cluster_id": cluster_1_res.data["id"]}),
+            data={"host_id": host_1_res.data["id"]},
+            content_type=APPLICATION_JSON,
+        )
+
+        service_prototype = Prototype.objects.create(
+            bundle=self.bundle,
+            type="service",
+            display_name="new_test_service",
+        )
+        service = ClusterObject.objects.create(
+            prototype=service_prototype,
+            cluster_id=cluster_1_res.data["id"],
+        )
+        self.client.post(
+            path=reverse("service", kwargs={"cluster_id": cluster_1_res.data["id"]}),
+            data={
+                "service_id": service.pk,
+                "prototype_id": service_prototype.pk,
+            },
+            content_type=APPLICATION_JSON,
+        )
+
+        self.assertFalse(AuditObject.objects.filter(is_deleted=True))
+
+        self.client.delete(
+            path=reverse("cluster-details", kwargs={"cluster_id": cluster_1_res.data["id"]})
+        )
+
+        self.assertEqual(AuditObject.objects.filter(is_deleted=True).count(), 1)
+
     def test_delete(self):
         self.client.delete(path=reverse("cluster-details", kwargs={"cluster_id": self.cluster.pk}))
 
diff --git a/python/audit/tests/test_host.py b/python/audit/tests/test_host.py
index 6c175434e0..ebf7864c3c 100644
--- a/python/audit/tests/test_host.py
+++ b/python/audit/tests/test_host.py
@@ -87,8 +87,8 @@ def test_create(self):
         res: Response = self.client.post(
             path=reverse("host"),
             data={
-                "prototype_id": self.host_prototype.id,
-                "provider_id": self.provider.id,
+                "prototype_id": self.host_prototype.pk,
+                "provider_id": self.provider.pk,
                 "fqdn": self.fqdn,
             },
         )
diff --git a/python/audit/tests/test_provider.py b/python/audit/tests/test_provider.py
index 4c92fca95c..9b14bfee73 100644
--- a/python/audit/tests/test_provider.py
+++ b/python/audit/tests/test_provider.py
@@ -51,7 +51,7 @@ def test_create(self):
             path=reverse("provider"),
             data={
                 "name": self.name,
-                "prototype_id": self.prototype.id,
+                "prototype_id": self.prototype.pk,
             },
         )
 
diff --git a/python/cm/unit_tests/models/test_action.py b/python/cm/unit_tests/models/test_action.py
index 1da914afa5..0488bb9ff3 100644
--- a/python/cm/unit_tests/models/test_action.py
+++ b/python/cm/unit_tests/models/test_action.py
@@ -10,10 +10,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
 from cm.unit_tests import utils
-
+from django.test import TestCase
 
 plausible_action_variants = {
     'unlimited': {
diff --git a/python/cm/unit_tests/models/test_adcm_entity.py b/python/cm/unit_tests/models/test_adcm_entity.py
index 93766ff165..a2b0939433 100644
--- a/python/cm/unit_tests/models/test_adcm_entity.py
+++ b/python/cm/unit_tests/models/test_adcm_entity.py
@@ -10,10 +10,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
 from cm import models
 from cm.unit_tests import utils
+from django.test import TestCase
 
 
 class ADCMEntityConcernTest(TestCase):
diff --git a/python/cm/unit_tests/models/test_message_template.py b/python/cm/unit_tests/models/test_message_template.py
index 1bbc88ff29..c0e6e3f1eb 100644
--- a/python/cm/unit_tests/models/test_message_template.py
+++ b/python/cm/unit_tests/models/test_message_template.py
@@ -12,11 +12,10 @@
 
 from uuid import uuid4
 
-from django.test import TestCase
-
 from cm import models
 from cm.errors import AdcmEx
 from cm.unit_tests import utils
+from django.test import TestCase
 
 
 class MessageTemplateTest(TestCase):
diff --git a/python/cm/unit_tests/models/test_task_log.py b/python/cm/unit_tests/models/test_task_log.py
index 159cbf0b13..465e0f5140 100644
--- a/python/cm/unit_tests/models/test_task_log.py
+++ b/python/cm/unit_tests/models/test_task_log.py
@@ -10,10 +10,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
 from cm import models
 from cm.unit_tests import utils
+from django.test import TestCase
 
 
 class TaskLogLockTest(TestCase):
diff --git a/python/cm/unit_tests/test_config_group.py b/python/cm/unit_tests/test_config_group.py
index ae39e9c66e..11eb8309ce 100644
--- a/python/cm/unit_tests/test_config_group.py
+++ b/python/cm/unit_tests/test_config_group.py
@@ -11,12 +11,11 @@
 # limitations under the License.
 import copy
 
+from cm.models import ConfigLog, GroupConfig
+from cm.unit_tests import utils
 from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
 
-from cm.models import GroupConfig, ConfigLog
-from cm.unit_tests import utils
-
 
 class GroupConfigTest(TestCase):
     """Tests `GroupConfig` model"""
diff --git a/python/cm/unit_tests/test_hierarchy.py b/python/cm/unit_tests/test_hierarchy.py
index a4ee4ac2d7..58964b77e6 100644
--- a/python/cm/unit_tests/test_hierarchy.py
+++ b/python/cm/unit_tests/test_hierarchy.py
@@ -12,10 +12,10 @@
 
 import time
 from unittest import skip
-from django.test import TestCase
 
 from cm import hierarchy
 from cm.unit_tests import utils
+from django.test import TestCase
 
 
 def generate_hierarchy():  # pylint: disable=too-many-locals,too-many-statements
diff --git a/python/cm/unit_tests/test_issue.py b/python/cm/unit_tests/test_issue.py
index cdc1c4d6f7..7055fc60fd 100644
--- a/python/cm/unit_tests/test_issue.py
+++ b/python/cm/unit_tests/test_issue.py
@@ -12,12 +12,10 @@
 
 from unittest.mock import patch
 
-from django.test import TestCase
-
 from cm import issue, models
-from cm.unit_tests import utils
 from cm.hierarchy import Tree
-
+from cm.unit_tests import utils
+from django.test import TestCase
 
 mock_issue_check_map = {
     models.ConcernCause.Config: lambda x: False,
diff --git a/python/cm/unit_tests/tests_adcm_config.py b/python/cm/unit_tests/tests_adcm_config.py
index b0d60bd159..386a5935c4 100644
--- a/python/cm/unit_tests/tests_adcm_config.py
+++ b/python/cm/unit_tests/tests_adcm_config.py
@@ -10,11 +10,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from unittest.mock import Mock, patch, call
-
-from django.test import TestCase
+from unittest.mock import Mock, call, patch
 
 import cm.adcm_config
+from django.test import TestCase
 
 
 class TestAdcmConfig(TestCase):
diff --git a/python/cm/unit_tests/tests_api.py b/python/cm/unit_tests/tests_api.py
index 4e1d9b42e1..ca1e894f49 100644
--- a/python/cm/unit_tests/tests_api.py
+++ b/python/cm/unit_tests/tests_api.py
@@ -13,13 +13,12 @@
 
 from unittest.mock import patch
 
-from django.test import TestCase
-from django.utils import timezone
-
 import cm.api as api_module
 import cm.hierarchy
 from cm import models
 from cm.unit_tests import utils
+from django.test import TestCase
+from django.utils import timezone
 
 
 class TestApi(TestCase):
diff --git a/python/cm/unit_tests/tests_bundle_deletion.py b/python/cm/unit_tests/tests_bundle_deletion.py
index cbff5376a0..eeb6b63c28 100644
--- a/python/cm/unit_tests/tests_bundle_deletion.py
+++ b/python/cm/unit_tests/tests_bundle_deletion.py
@@ -1,10 +1,9 @@
-from django.test import TestCase
-
 import cm.api
-from cm.unit_tests.tests_upgrade import SetUp
-from cm.errors import AdcmEx
 from cm.bundle import delete_bundle
+from cm.errors import AdcmEx
 from cm.models import Host
+from cm.unit_tests.tests_upgrade import SetUp
+from django.test import TestCase
 
 
 class TestHC(TestCase):
diff --git a/python/cm/unit_tests/tests_hc.py b/python/cm/unit_tests/tests_hc.py
index c0c7bd46bc..375ba25a02 100644
--- a/python/cm/unit_tests/tests_hc.py
+++ b/python/cm/unit_tests/tests_hc.py
@@ -10,13 +10,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
 import cm.api
 import cm.job
-from cm.models import Host, ClusterObject, ServiceComponent, Action
 from cm.errors import AdcmEx
+from cm.models import Action, ClusterObject, Host, ServiceComponent
 from cm.unit_tests.tests_upgrade import SetUp
+from django.test import TestCase
 
 
 class TestHC(TestCase):
diff --git a/python/cm/unit_tests/tests_inventory.py b/python/cm/unit_tests/tests_inventory.py
index dab2be6356..126e1bbcab 100644
--- a/python/cm/unit_tests/tests_inventory.py
+++ b/python/cm/unit_tests/tests_inventory.py
@@ -10,13 +10,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from unittest.mock import patch, Mock
-
-from django.test import TestCase
-from django.utils import timezone
+from unittest.mock import Mock, patch
 
 import cm.inventory
 from cm import models
+from django.test import TestCase
+from django.utils import timezone
 
 
 class TestInventory(TestCase):
diff --git a/python/cm/unit_tests/tests_issue.py b/python/cm/unit_tests/tests_issue.py
index f7c2b3870f..4c5c54788a 100644
--- a/python/cm/unit_tests/tests_issue.py
+++ b/python/cm/unit_tests/tests_issue.py
@@ -10,11 +10,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
 import cm.api
 import cm.issue
-from cm.models import Bundle, Prototype, PrototypeImport, ClusterBind, ConcernCause
+from cm.models import Bundle, ClusterBind, ConcernCause, Prototype, PrototypeImport
+from django.test import TestCase
 
 
 class TestImport(TestCase):
diff --git a/python/cm/unit_tests/tests_job.py b/python/cm/unit_tests/tests_job.py
index 7e4093aff7..159304de6b 100644
--- a/python/cm/unit_tests/tests_job.py
+++ b/python/cm/unit_tests/tests_job.py
@@ -13,17 +13,15 @@
 # pylint: disable=protected-access
 
 import os
-from unittest.mock import patch, Mock
-
-from django.test import TestCase
-from django.utils import timezone
+from unittest.mock import Mock, patch
 
 import cm
-from cm import config
 import cm.job as job_module
-from cm import models
+from cm import config, models
 from cm.logger import log
 from cm.unit_tests import utils
+from django.test import TestCase
+from django.utils import timezone
 
 
 class TestJob(TestCase):
diff --git a/python/cm/unit_tests/tests_runner.py b/python/cm/unit_tests/tests_runner.py
index 061cc30655..640aac87b6 100644
--- a/python/cm/unit_tests/tests_runner.py
+++ b/python/cm/unit_tests/tests_runner.py
@@ -10,9 +10,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.utils import timezone
 from cm.bundle import load_adcm
-from cm.models import TaskLog, JobLog, Bundle, Prototype, Action
+from cm.models import Action, Bundle, JobLog, Prototype, TaskLog
+from django.utils import timezone
 
 
 class PreparationData:
diff --git a/python/cm/unit_tests/tests_upgrade.py b/python/cm/unit_tests/tests_upgrade.py
index 7b6ee8e65e..841175c522 100644
--- a/python/cm/unit_tests/tests_upgrade.py
+++ b/python/cm/unit_tests/tests_upgrade.py
@@ -10,8 +10,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
 import cm.api
 import cm.job
 import cm.upgrade
@@ -20,16 +18,17 @@
 from cm.models import (
     Bundle,
     ClusterObject,
+    ConcernCause,
     ConfigLog,
     Host,
     HostComponent,
-    ConcernCause,
     Prototype,
     PrototypeConfig,
     ServiceComponent,
     Upgrade,
 )
 from cm.unit_tests import utils
+from django.test import TestCase
 
 
 class TestUpgradeVersion(TestCase):
diff --git a/python/cm/unit_tests/tests_variant.py b/python/cm/unit_tests/tests_variant.py
index 2d3393b6d6..f19a15c04d 100644
--- a/python/cm/unit_tests/tests_variant.py
+++ b/python/cm/unit_tests/tests_variant.py
@@ -10,13 +10,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from django.test import TestCase
-
-from cm.api import add_host, add_host_to_cluster, add_host_provider
-from cm.variant import get_variant, variant_host, var_host_solver, VARIANT_HOST_FUNC
+from cm.api import add_host, add_host_provider, add_host_to_cluster
 from cm.errors import AdcmEx
-from cm.models import Cluster, ClusterObject, ServiceComponent, HostComponent
-from cm.models import Bundle, Prototype
+from cm.models import (
+    Bundle,
+    Cluster,
+    ClusterObject,
+    HostComponent,
+    Prototype,
+    ServiceComponent,
+)
+from cm.variant import VARIANT_HOST_FUNC, get_variant, var_host_solver, variant_host
+from django.test import TestCase
 
 
 def cook_cluster():