def import_extra_data(self, obj, extra_data, fields): """Import new extra_data content from the client. There are three methods for injecting new content into the object's ``extra_data`` JSON field: 1. Simple key/value forms through setting :samp:`extra_data.{key}={value}`. This will convert boolean-like strings to booleans, numeric strings to integers or floats, and the rest are stored as strings. It's only intended for very simple data. 2. A JSON Merge Patch document through setting :samp:`extra_data:json={patch}`. This is a simple way of setting new structured JSON content. 3. A more complex JSON Patch document through setting :samp:`extra_data:json-patch={patch}`. This is a more advanced way of manipulating JSON data, allowing for sanity-checking of existing content, adding new keys/array indices, replacing existing keys/indices, deleting data, or copying/moving data. If any operation (including the sanity-checking) fails, the whole patch is aborted. All methods respect any access states that apply to the resource, and forbid both writing to keys starting with ``__`` and replacing the entire root of ``extra_data``. .. versionchanged:: 3.0 Added support for ``extra_data:json`` and ``extra_data:json-patch``. Args: obj (django.db.models.Model): The object containing an ``extra_data`` field. extra_data (dict): The existing contents of the ``extra_data`` field. This will be updated directly. fields (dict): The fields being set in the request. This will be checked for ``extra_data:json``, ``extra_data:json-patch``, and any beginning with ``extra_data.``. Returns: bool: ``True`` if ``extra_data`` was at all modified. ``False`` if it wasn't. Raises: ImportExtraDataError: There was an error importing content into ``extra_data``. There may be a parse error or access error. Details are in the message. """ updated = False # Check for a JSON Merge Patch. This is the simplest way to update # extra_data with new structured JSON content. if 'extra_data:json' in fields: try: patch = json.loads(fields['extra_data:json']) except ValueError as e: raise ImportExtraDataError( _('Could not parse JSON data: %s') % e) new_extra_data = json_merge_patch( extra_data, patch, can_write_key_func=lambda path, **kwargs: self. _can_write_extra_data_key(obj, path)) # Save extra_data only if it remains a dictionary, so callers # can't replace the entire contents. if not isinstance(new_extra_data, dict): raise ImportExtraDataError( _('extra_data:json cannot replace extra_data with a ' 'non-dictionary type')) extra_data.clear() extra_data.update(new_extra_data) updated = True # Check for a JSON Patch. This is more advanced, and can be used in # conjunction with the JSON Merge Patch. if 'extra_data:json-patch' in fields: try: patch = json.loads(fields['extra_data:json-patch']) except ValueError as e: raise ImportExtraDataError( _('Could not parse JSON data: %s') % e) try: new_extra_data = json_patch( extra_data, patch, can_read_key_func=self._can_read_extra_data_key, can_write_key_func=lambda path, **kwargs: self. _can_write_extra_data_key(obj, path)) extra_data.clear() extra_data.update(new_extra_data) updated = True except JSONPatchError as e: raise ImportExtraDataError( _('Failed to patch JSON data: %s') % e) # Support setting individual keys to simple values. This is the older # method of setting JSON data, and is no longer recommended for new # clients. for key, value in fields.items(): if key.startswith('extra_data.'): key = key[EXTRA_DATA_LEN:] if self._can_write_extra_data_key(obj, (key, )): if value != '': if value in ('true', 'True', 'TRUE'): value = True elif value in ('false', 'False', 'FALSE'): value = False else: try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass extra_data[key] = value updated = True elif key in extra_data: del extra_data[key] updated = True return updated
def import_extra_data(self, obj, extra_data, fields): """Import new extra_data content from the client. There are three methods for injecting new content into the object's ``extra_data`` JSON field: 1. Simple key/value forms through setting :samp:`extra_data.{key}={value}`. This will convert boolean-like strings to booleans, numeric strings to integers or floats, and the rest are stored as strings. It's only intended for very simple data. 2. A JSON Merge Patch document through setting :samp:`extra_data:json={patch}`. This is a simple way of setting new structured JSON content. 3. A more complex JSON Patch document through setting :samp:`extra_data:json-patch={patch}`. This is a more advanced way of manipulating JSON data, allowing for sanity-checking of existing content, adding new keys/array indices, replacing existing keys/indices, deleting data, or copying/moving data. If any operation (including the sanity-checking) fails, the whole patch is aborted. All methods respect any access states that apply to the resource, and forbid both writing to keys starting with ``__`` and replacing the entire root of ``extra_data``. .. versionchanged:: 3.0 Added support for ``extra_data:json`` and ``extra_data:json-patch``. Args: obj (django.db.models.Model): The object containing an ``extra_data`` field. extra_data (dict): The existing contents of the ``extra_data`` field. This will be updated directly. fields (dict): The fields being set in the request. This will be checked for ``extra_data:json``, ``extra_data:json-patch``, and any beginning with ``extra_data.``. Raises: ImportExtraDataError: There was an error importing content into ``extra_data``. There may be a parse error or access error. Details are in the message. """ # Check for a JSON Merge Patch. This is the simplest way to update # extra_data with new structured JSON content. if 'extra_data:json' in fields: try: patch = json.loads(fields['extra_data:json']) except ValueError as e: raise ImportExtraDataError(_('Could not parse JSON data: %s') % e) new_extra_data = json_merge_patch( extra_data, patch, can_write_key_func=lambda path, **kwargs: self._can_write_extra_data_key(obj, path)) # Save extra_data only if it remains a dictionary, so callers # can't replace the entire contents. if not isinstance(new_extra_data, dict): raise ImportExtraDataError( _('extra_data:json cannot replace extra_data with a ' 'non-dictionary type')) extra_data.clear() extra_data.update(new_extra_data) # Check for a JSON Patch. This is more advanced, and can be used in # conjunction with the JSON Merge Patch. if 'extra_data:json-patch' in fields: try: patch = json.loads(fields['extra_data:json-patch']) except ValueError as e: raise ImportExtraDataError(_('Could not parse JSON data: %s') % e) try: new_extra_data = json_patch( extra_data, patch, can_read_key_func=self._can_read_extra_data_key, can_write_key_func=lambda path, **kwargs: self._can_write_extra_data_key(obj, path)) extra_data.clear() extra_data.update(new_extra_data) except JSONPatchError as e: raise ImportExtraDataError(_('Failed to patch JSON data: %s') % e) # Support setting individual keys to simple values. This is the older # method of setting JSON data, and is no longer recommended for new # clients. for key, value in six.iteritems(fields): if key.startswith('extra_data.'): key = key[EXTRA_DATA_LEN:] if self._can_write_extra_data_key(obj, (key,)): if value != '': if value in ('true', 'True', 'TRUE'): value = True elif value in ('false', 'False', 'FALSE'): value = False else: try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass extra_data[key] = value elif key in extra_data: del extra_data[key]