def _get_subfield_defaults(cls): total = {"data": {}, "computed": {"_mjson_revert_patch": None}} for k, v in cls.subfields["data"].items(): total["data"][k] = json_encode(v.default) for k, v in cls.subfields["computed"].items(): total["computed"][k] = json_encode(v.default) return total
def patch_json_field(self): if self.status == Status.DELETED: return assert self.status in (Status.NORMAL, Status.WORKING), self.status total_default = self.subfield_defaults _schema_changed = False old_status = self.__dict__["status"] for json_field_name in total_default: json_field = self.__dict__[json_field_name] field_default = total_default[json_field_name] for key, value in field_default.items(): if key not in json_field: if callable(value): value = safe_call(value, self) json_field[key] = json_deepcopy_with_callable(value) _schema_changed = True self.__dict__["status"] = Status.DIRTY self._mjson_revert_patch.update( {"status": json_encode(old_status), json_field_name: {"_schema_changed": _schema_changed}} ) for k in [k for k in json_field.keys() if k not in field_default]: del json_field[k] _schema_changed = True self.__dict__["status"] = Status.DIRTY self._mjson_revert_patch.update( {"status": json_encode(old_status), json_field_name: {"_schema_changed": _schema_changed}} ) if _schema_changed and settings.IS_UNIT_TEST: self._mjson_on_init = deepcopy(self.mjson) self._mjson_on_init["status"] = json_encode(old_status)
def _init_no_inited_subfields(self): for k, v in self.data.items(): if v is None and k[0] != "_": subfield = self.get_subfield(k) assert subfield.null, console_log("{} 는 null 이 허용되지 않습니다.".format(subfield.subfield_name)) elif callable(v): self.data[k] = json_encode(safe_call(v, self)) for k, v in self.computed.items(): if v is None and k[0] != "_": subfield = self.get_subfield(k) assert subfield.null elif callable(v): self.computed[k] = json_encode(safe_call(v, self))
def test_data_setter(self): instance = Dummy() old_computed = deepcopy(instance.computed) old_data = deepcopy(instance.data) with ForceChanger(instance): instance.uri = "/uri/1/" assert instance.computed["uri"] == "/uri/1/" old_computed["uri"] = "/uri/1/" old_computed["uri_hash"] = json_encode(instance.uri_hash) assert instance.computed == old_computed instance.data = {"temp": 1} assert instance.data["temp"] == 1 old_data["temp"] = 1 assert instance.data == old_data instance.data = {"temp": 0} assert instance.data["temp"] == 0 old_data["temp"] = 0 assert instance.data == old_data instance.monitors.append(0) assert instance.data["monitors"] == [0] old_data["monitors"] = [0] assert instance.data == old_data
def __get__(self, instance, owner): if not instance: return self field_name, subfield_name, subfield_type, expire = ( self.field_name, self.subfield_name, self.subfield_type, self.expire, ) json_field = getattr(instance, field_name) value = json_field[subfield_name] if callable(value): try: value = value(instance) except Exception as e: msg = "{}: {}".format(e.__class__.__name__, str(e)) if "NoneType" in msg or "DoesNotExist" in msg: return None else: raise e assert not callable(value) assert self.check_schema(value) value = json_encode(value) json_field[subfield_name] = value value_decoded = json_decode(value, subfield_type) return value_decoded
def onchange_subfield(self, field_name, subfield_name, old, new): assert old != new, "old 와 new 값이 같은 경우는 원천적으로 발생되지 않도록 해야 합니다." old_status, _mjson_revert_patch, subfields = (self.status, self._mjson_revert_patch, self.subfields) subfield = subfields[field_name][subfield_name] assert not isinstance(subfield, SubfieldWrapper) if old_status == Status.CREATING: # TODO : save() 호출 자체를 없애고 나면 이것도 없애기 # 이때는 on_create 이후 별도로 onchange 이벤트를 일괄 발생시킴 return if subfield_name not in _mjson_revert_patch[field_name]: _mjson_revert_patch[field_name][subfield_name] = json_encode(old) with ForceChanger(self): if old_status not in (Status.NEW, Status.NO_SYNC): self.status = Status.DIRTY self._process_dependent_computed(subfield_name) # original onchange_func_name = ONCHANGE_FUNC_NAME.format(subfield_name) func = getattr(self, onchange_func_name, None) func and func(old, new) # wrappers for wrapper in subfields["_wrapper_reverse"].get(subfield_name, []): onchange_func_name = ONCHANGE_FUNC_NAME.format(wrapper.wrapper_subfield_name) func = getattr(self, onchange_func_name, None) func and func(old, new) self._process_expire_onchange(subfield_name)
def test_data_setter(self): with Transaction() as tran: instance = Dummy.objects.create() old_data = deepcopy(instance.data) old_computed = deepcopy(instance.computed) old_transaction_id = tran.id with tran: with ForceChanger(instance): instance.uri = "/uri/1/" instance.save() with Transaction(): instance_old = instance instance = Dummy.objects.get(id=instance.id) assert id(instance_old) != id(instance) assert instance.computed["uri"] == "/uri/1/" old_computed["uri"] = "/uri/1/" old_computed["uri_hash"] = json_encode(instance.uri_hash) old_computed["_mjson_revert_patch"] = { "data": {}, "status": 10, "version": 1, "computed": { "uri": None, "uri_hash": None }, "computed_uri_hash": None, "last_transaction": old_transaction_id, } assert instance.computed == old_computed instance.data = {"temp": 1} instance.save() with Transaction(): instance_old = instance instance = Dummy.objects.get(id=instance.id) assert id(instance_old) != id(instance) assert instance.data["temp"] == 1 old_data["temp"] = 1 assert instance.data == old_data instance.data = {"temp": 0} instance.save() with Transaction(): instance_old = instance instance = Dummy.objects.get(id=instance.id) assert id(instance_old) != id(instance) assert instance.data["temp"] == 0 old_data["temp"] = 0 assert instance.data == old_data instance.monitors.append(0) instance.save() with Transaction(): instance_old = instance instance = Dummy.objects.get(id=instance.id) assert id(instance_old) != id(instance) assert instance.data["monitors"] == [0] old_data["monitors"] = [0] assert instance.data == old_data
def convert_filter(self, model, parts, value, lookup_type, raw): if self.filter: assert lookup_type == "exact", console_log( "filter 가 지정된 subfield 에 대해서는 exact 검색만 가능합니다.", lookup_type) return self.filter(model, value) assert lookup_type not in ("isnull", ), console_log( "subfield 에 대해 지원되지 않는 lookup_type 입니다.", lookup_type) return { "{}__{}__{}".format(self.field_name, LOOKUP_SEP.join(parts), lookup_type): json_encode(value) }
def __set__(self, instance, patch): instance.assert_changeable(self.field_name) assert self.check_schema(patch) d = super().__get__(instance, instance.__class__) old = d[:] # address 가 바뀌면 안되기 때문에 성능을 약간 희생해서라도 이렇게 구현 d.clear() patch = [json_encode(e) for e in patch] d.extend(patch) if old != d: instance.onchange_subfield(self.field_name, self.subfield_name, old, d) return old
def __set__(self, instance, value): cls, subfield_type, normalize, lf_name, field_name, subfield_name, create_only = ( instance.__class__, self.subfield_type, self.normalize, self.subfield_name, self.field_name, self.subfield_name, self.create_only, ) json_field = getattr(instance, field_name) if normalize and value is not None: old = value value = normalize(value) if instance.status == Status.NEW and old != value: if instance.raw is None: instance.raw = {} instance.raw[lf_name] = old # value 를 subfield_type 으로 변환. 따라서 json_decode() 는 형변환도 구현해야 함 value = json_decode(value, subfield_type) # old old = json_field[subfield_name] if callable(old): old = None old = json_decode(old, subfield_type) if old != value: # TODO : if 블럭 밖에 있으면 에러 발생. 장고 내부 코드 확인 후 조처 ( django.db.models.query ) """ if annotation_col_map: for attr_name, col_pos in annotation_col_map.items(): setattr(obj, attr_name, row[col_pos]) """ if create_only: assert instance.status in (Status.CREATING, Status.NEW), console_log( "{}.{} = {} ({})".format( self.owner.__name__, self.subfield_name, value, instance.status)) assert old is None assert value is not None assert self.check_schema(value), console_log( "{}.check_schema({}) is fail!".format(self.subfield_name, value)) instance.assert_changeable(field_name) json_field[subfield_name] = json_encode(value) instance.onchange_subfield(field_name, subfield_name, old, value) return old
def __setattr__(self, field_name, value): # __dict__ setter if field_name[0] == "_" or not self._is_initialized: self.__dict__[field_name] = value return # object setter # TODO : 모두 확정된 후 로컬상수변수로 대체하여 튜닝 if field_name in self.field_names_needs_object_setter: return object.__setattr__(self, field_name, value) # for overrides super().delete() if field_name == "id" and value is None: return # set field old = getattr(self, field_name) if old != value: # for django admin if value is None and field_name in ("data", "computed"): return # check pre condition self.assert_changeable(field_name) # change value if field_name == "data": for subfield, subvalue in value.items(): setattr(self, subfield, subvalue) elif field_name == "status": assert old.check_route(value), "해당 status 변경은 허용되지 않습니다: {} --> {}".format(old, value) self.__dict__["status"] = value elif field_name == "computed": assert not settings.IS_UNIT_TEST, "computed field 직접 세팅은 허용되지 않습니다." # for django admin console_log("try change of computed field is ignored") else: if self.status in (Status.NORMAL, Status.WORKING): self.__setattr__("status", Status.DIRTY) self.__dict__[field_name] = value # make revert patch if field_name not in self._mjson_revert_patch and field_name in self.field_names: self._mjson_revert_patch[field_name] = json_encode(old) # raw if field_name == "raw": assert self.status in (Status.CREATING, Status.NEW) self.init_data_by_raw()
def remove(self, value): encoded = json_encode(value) index = self._d.index(encoded) del self._d[index] return index
def append(self, value): self._d.append(json_encode(value))
def __setitem__(self, key, value): self._d[key] = json_encode(value)
def encoder(d, k, v): patch[k] = json_encode(v)
def __setattr__(self, key, value): if key[0] == "_": self.__dict__[key] = json_encode(value) return patch = {key: value} self._setter(patch)
def mjson(self): mjson = {} for field_name in self.field_names: mjson[field_name] = json_encode(getattr(self, field_name)) return mjson
def encoder(d, k, v, storage): patch[k] = json_encode(v)