Implement AC-667... user serializer to determine read-only fields and use those to decide what to populate in activity stream create instances AND when to create active stream update instances

This commit is contained in:
Matthew Jones
2014-01-23 15:44:30 -05:00
parent 54d7884ac6
commit 2712643c62
2 changed files with 43 additions and 12 deletions

View File

@@ -13,6 +13,7 @@ from django.dispatch import receiver
# AWX # AWX
from awx.main.models import * from awx.main.models import *
from awx.api.serializers import *
from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore
__all__ = [] __all__ = []
@@ -186,6 +187,18 @@ def update_host_last_job_after_job_deleted(sender, **kwargs):
# Set via ActivityStreamRegistrar to record activity stream events # Set via ActivityStreamRegistrar to record activity stream events
model_serializer_mapping = {Organization: OrganizationSerializer,
Inventory: InventorySerializer,
Host: HostSerializer,
Group: GroupSerializer,
InventorySource: InventorySourceSerializer,
Credential: CredentialSerializer,
Team: TeamSerializer,
Project: ProjectSerializer,
Permission: PermissionSerializer,
JobTemplate: JobTemplateSerializer,
Job: JobSerializer}
def activity_stream_create(sender, instance, created, **kwargs): def activity_stream_create(sender, instance, created, **kwargs):
if created: if created:
# TODO: Rethink details of the new instance # TODO: Rethink details of the new instance
@@ -193,7 +206,7 @@ def activity_stream_create(sender, instance, created, **kwargs):
activity_entry = ActivityStream( activity_entry = ActivityStream(
operation='create', operation='create',
object1=object1, object1=object1,
changes=json.dumps(model_to_dict(instance))) changes=json.dumps(model_to_dict(instance, model_serializer_mapping)))
activity_entry.save() activity_entry.save()
getattr(activity_entry, object1).add(instance) getattr(activity_entry, object1).add(instance)
@@ -209,7 +222,9 @@ def activity_stream_update(sender, instance, **kwargs):
return return
new = instance new = instance
changes = model_instance_diff(old, new) changes = model_instance_diff(old, new, model_serializer_mapping)
if changes is None:
return
object1 = camelcase_to_underscore(instance.__class__.__name__) object1 = camelcase_to_underscore(instance.__class__.__name__)
activity_entry = ActivityStream( activity_entry = ActivityStream(
operation='update', operation='update',

View File

@@ -225,10 +225,12 @@ def update_scm_url(scm_type, url, username=True, password=True,
parts.query, parts.fragment]) parts.query, parts.fragment])
return new_url return new_url
def model_instance_diff(old, new): def model_instance_diff(old, new, serializer_mapping=None):
""" """
Calculate the differences between two model instances. One of the instances may be None (i.e., a newly Calculate the differences between two model instances. One of the instances may be None (i.e., a newly
created model or deleted model). This will cause all fields with a value to have changed (from None). created model or deleted model). This will cause all fields with a value to have changed (from None).
serializer_mapping are used to determine read-only fields.
When provided, read-only fields will not be included in the resulting dictionary
""" """
from django.db.models import Model from django.db.models import Model
from awx.main.models.organization import Credential from awx.main.models.organization import Credential
@@ -249,28 +251,42 @@ def model_instance_diff(old, new):
else: else:
fields = set() fields = set()
for field in fields: if serializer_mapping is not None and new.__class__ in serializer_mapping:
old_value = str(getattr(old, field.name, None)) serializer_actual = serializer_mapping[new.__class__]()
new_value = str(getattr(new, field.name, None)) allowed_fields = [x for x in serializer_actual.fields if not serializer_actual.fields[x].read_only] + ['id']
else:
allowed_fields = [x.name for x in new._meta.fields]
if old_value != new_value and field.name not in Credential.PASSWORD_FIELDS: for field in allowed_fields:
diff[field.name] = (old_value, new_value) old_value = str(getattr(old, field, None))
elif field.name in Credential.PASSWORD_FIELDS: new_value = str(getattr(new, field, None))
diff[field.name] = ("hidden", "hidden")
if old_value != new_value and field not in Credential.PASSWORD_FIELDS:
diff[field] = (old_value, new_value)
elif old_value != new_value and field in Credential.PASSWORD_FIELDS:
diff[field] = ("hidden", "hidden")
if len(diff) == 0: if len(diff) == 0:
diff = None diff = None
return diff return diff
def model_to_dict(obj): def model_to_dict(obj, serializer_mapping=None):
""" """
Serialize a model instance to a dictionary as best as possible Serialize a model instance to a dictionary as best as possible
serializer_mapping are used to determine read-only fields.
When provided, read-only fields will not be included in the resulting dictionary
""" """
from awx.main.models.organization import Credential from awx.main.models.organization import Credential
attr_d = {} attr_d = {}
if serializer_mapping is not None and obj.__class__ in serializer_mapping:
serializer_actual = serializer_mapping[obj.__class__]()
allowed_fields = [x for x in serializer_actual.fields if not serializer_actual.fields[x].read_only] + ['id']
else:
allowed_fields = [x.name for x in obj._meta.fields]
for field in obj._meta.fields: for field in obj._meta.fields:
# FIXME: This needs to be aware of fields not to be included in the AS delta log if field.name not in allowed_fields:
continue
if field.name not in Credential.PASSWORD_FIELDS: if field.name not in Credential.PASSWORD_FIELDS:
attr_d[field.name] = str(getattr(obj, field.name, None)) attr_d[field.name] = str(getattr(obj, field.name, None))
else: else: