内容简介:自定义一个Django的Model Filed--StaticJSONField
使用JSON保存用户数据的问题
有时候我们会用json来保存一些杂乱的数据,比如一个用户的各种信息,这些信息被序列化为一个json字符串并最终存放在数据库的一个column里面。
user.config = {
"accept_notification": True,
"accept_invitation": True,
"level": "golden"
}
使用json保存这些配置的好处在于,添加或者修改属性的时候你不需要对数据库进行修改。在关系型数据库看来,这个config不过是一个字符串而已。
但是这样做也会有一些问题,比如:
-
有时候写了错别字,依然可以正常地保存到数据库:
user.config["leel"] = “golden” - 删除json里面的字段时需要遍历数据库,将所有的json里面对应字段删除,不然它们会一直占用空间。
-
获取数据时,往往需要添加一个默认值:
user.config.get("level", "golden"),因为你无法确认level是否一定在config里面。
使用 __getattr__
以及 __setattr__
的一个解决方案
__getattr__
和 __setattr__
是 python 的魔术方法,用它可以对自定义的类添加一些特殊的效果。
调用instance.atttibute的时候, 如果这个attribute没有明确的在instance所属的类里面明确定义,就会触发对 __getattr__
的访问。调用instance.attribute = value的时候,必然会命中对 __setattr__
的访问。基于这两个原理,我们可以编写这么一个类:
class InvalidAttributeException(Exception):
pass
class StaticJSON(object):
def __init__(self, default, value=None):
value = value or {}
self.__dict__["_default"] = default
self.__dict__["_value"] = value
for k, v in self._default.items():
self.__dict__["_value"].setdefault(k, v)
def __getattr__(self, name):
if name in self.__dict__.get("_default", {}):
return self.__dict__["_value"][name]
else:
raise AttributeError(name)
def __setattr__(self, name, value):
if name == "_default" or name == "_value":
setattr(self.__dict__[name], name, value)
if name not in self.__dict__["_default"]:
raise InvalidAttributeException("{} is not a valid attribute, try {} instead".format(
name, ", ".join(self.__dict__["_default"].keys())
))
self.__dict__["_value"][name] = value
def items(self):
""" used for serializer.to_representation
"""
return self._value.items()
def __iter__(self):
""" for i in StaticJSON:
pass
"""
for item in self._value:
yield item
看着上面代码有一点头晕,这里我们举一个例子,我们可以这么使用这个类:
# 用户的默认属性
default= {
"accept_invitation": True,
"accept_notification": False,
"level": "golden"
}
# 用户的属性
value = {
"level": "diamond"
}
config = StaticJSON(default=default, value=value)
# 因为value里面明确定义了level是diamond,因此这边获取到的是value的值
config.level
>>>diamond
config.level = "golden"
# 用户没有定义accept_notification 属性,因此返回的是默认值
config.accept_notification
>>> False
# 调用未定义的属性, 报错
config.non_exist_attribute = "some attribute"
>>> Traceback (most recent call last):
File "<input>", line 1, in <module>
config.non_exist_attribute = "some attribute"
File "static_json.py", line 34, in __setattr__
raise InvalidAttributeException("{} is not a valid attribute, try {} instead".format(
__console__.InvalidAttributeException: non_exist_attribute is not a valid attribute, try accept_invitation, accept_notification, level instead
# 像字典一样对这个instance迭代:
for key in config:
print(key, getattr(config, key))
for key,value in config.items():
print(key, value)
总而言之,我们做了这几件事情:
-
__init__函数接受默认值(default)以及实际值(value),并将这些数据分别保存到<StaticJSON instance>._default以及<StaticJSON instance>._value里面. 如果传入的default里面有一些attribute在value里面不存在,那么就用default去填充它。 -
编写
__getattr__方法,这个方法使得在获取值的时候(<StaticJSON instance>.attribute), 实际从<StaticJSON instance>._value里面获取,如果找不到, 说明这个attribute不应该被访问,报错。 -
编写
__setattr__方法,在赋值的时候(<StaticJSON instance>.attribute = value), 先检查这个attribute是否符合被允许, 然后保存到<StaticJSON instance>._value里面。 -
编写
_iter_以及items方法吗,因此我们可以使用类似for key,value in instance.items():或者for key in instance两种方法对这个instance进行迭, 其实也就是让<StaticJSON instance>表现的更加像字典。
为了让这个类更加像字典, 我们还可以实现 __getitem__
, __setitem__
方法。
实现一个Django的自定义Field
上面的 StaticJSON
可以用来对数据进行修改,查询,但是不能够被直接定义到Djanog的model里面,为此我们需要实现一个StaticJSONField,具体的实现代码在 gist
里面。
一般来讲我们可以这样定义一个model:
from somewhere import StaticJSONField
class User(Model):
DEFAULT_CONFIG = {
"accept_invitation": True,
"accept_notification": False
"level": "golden"
}
name = models.CharField(max_length=20)
config = StaticJSONField(default=DEFAULT_SUMMARY)
print(user.config.level)
user.config.level = "diamond"
user.config.accept_notification = False
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Android 自定义 View (04自定义属性)
- Vue自定义组件(简单实现一个自定义组件)
- Android 自定义View:深入理解自定义属性(七)
- Qt编写自定义控件20-自定义饼图 原 荐
- SpringBoot2 | SpringBoot自定义AutoConfiguration | SpringBoot自定义starter(五)
- 『互联网架构』软件架构-springboot自定义视图和自定义Starter(90)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。