diff --git a/awx/main/fields.py b/awx/main/fields.py index 231b4b7ba9..ebe3cfc125 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -67,10 +67,48 @@ def __enum_validate__(validator, enums, instance, schema): Draft4Validator.VALIDATORS['enum'] = __enum_validate__ +import logging + +logger = logging.getLogger('awx.main.fields') + + class JSONBlob(JSONField): + # Cringe... a JSONField that is back ended with a TextField. + # This field was a legacy custom field type that tl;dr; was a TextField + # Over the years, with Django upgrades, we were able to go to a JSONField instead of the custom field + # However, we didn't want to have large customers with millions of events to update from text to json during an upgrade + # So we keep this field type as backended with TextField. def get_internal_type(self): return "TextField" + # postgres uses a Jsonb field as the default backend + # with psycopg2 it was using a psycopg2._json.Json class internally + # with psycopg3 it uses a psycopg.types.json.Jsonb class internally + # The binary class was not compatible with a text field, so we are going to override these next two methods and ensure we are using a string + + def from_db_value(self, value, expression, connection): + if value is None: + return value + + if isinstance(value, str): + try: + return json.loads(value) + except Exception as e: + logger.error(f"Failed to load JSONField {self.name}: {e}") + + return value + + def get_db_prep_value(self, value, connection, prepared=False): + if not prepared: + value = self.get_prep_value(value) + try: + return json.dumps(value) + except Exception as e: + logger.error(f"Failed to dump JSONField {self.name}: {e}") + logger.error(f"{value}") + + return value + # Based on AutoOneToOneField from django-annoying: # https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py