我在Django中展示了一个典型的关系数据库模型,其中一个典型模型包含一些ForeignKeys
、一些ManyToManyFields
和一些扩展了Django的DateTimeField
的字段。
我想保存从外部api接收的JSON格式(而不是平面格式)的数据。我不希望数据被保存到各自的表中(而不是将整个json字符串保存到一个字段中)。做这件事最干净、最简单的方法是什么?有没有库可以让这项任务变得更简单?
这里有一个例子来澄清我的问题,
型号-
class NinjaData(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
name = models.CharField(max_length=60)
birthdatetime = MyDateTimeField(null=True)
deathdatetime = MyDatetimeField(null=True)
skills = models.ManyToManyField(Skills, null=True)
weapons = models.ManyToManyField(Weapons, null=True)
master = models.ForeignKey(Master, null=True)
class Skills(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
name = models.CharField(max_length=60)
difficulty = models.IntegerField(null=True)
class Weapons(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
name = models.CharField(max_length=60)
weight = models.FloatField(null=True)
class Master(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
name = models.CharField(max_length=60)
is_awesome = models.NullBooleanField()
现在,我通常必须将从外部api (秘密忍者api)获得的json字符串数据保存到此模型中,json如下所示
JSON-
{
"id":"1234",
"name":"Hitori",
"birthdatetime":"11/05/1999 20:30:00",
"skills":[
{
"id":"3456",
"name":"stealth",
"difficulty":"2"
},
{
"id":"678",
"name":"karate",
"difficulty":"1"
}
],
"weapons":[
{
"id":"878",
"name":"shuriken",
"weight":"0.2"
},
{
"id":"574",
"name":"katana",
"weight":"0.5"
}
],
"master":{
"id":"4",
"name":"Schi fu",
"is_awesome":"true"
}
}
现在处理典型ManyToManyField的逻辑相当简单,
逻辑代码-
data = json.loads(ninja_json)
ninja = NinjaData.objects.create(id=data['id'], name=data['name'])
if 'weapons' in data:
weapons = data['weapons']
for weapon in weapons:
w = Weapons.objects.get_or_create(**weapon) # create a new weapon in Weapon table
ninja.weapons.add(w)
if 'skills' in data:
...
(skipping rest of the code for brevity)
有很多我可以使用的方法,
view
函数中的
__init__
方法save()
方法我想知道是否有一种最明显的方法可以将数据以这种json形式保存到数据库中,而不需要多次编写上面的逻辑,您建议的最优雅的方法是什么?
感谢大家阅读这篇长篇文章。
发布于 2011-12-05 02:01:45
在我看来,你需要的代码最干净的地方是作为NinjaData模型的自定义管理器上的一个新的管理器方法(例如from_json_string)。
我不认为你应该覆盖标准的create、get_or_create等方法,因为你正在做的事情与它们通常做的事情有点不同,保持它们正常工作是有好处的。
更新:我意识到我可能会在某个时候需要它,所以我编写了一个通用函数,并对其进行了简单的测试。由于它递归地遍历并影响其他模型,我不再确定它是否属于Manager方法,是否应该是一个独立的辅助函数。
def create_or_update_and_get(model_class, data):
get_or_create_kwargs = {
model_class._meta.pk.name: data.pop(model_class._meta.pk.name)
}
try:
# get
instance = model_class.objects.get(**get_or_create_kwargs)
except model_class.DoesNotExist:
# create
instance = model_class(**get_or_create_kwargs)
# update (or finish creating)
for key,value in data.items():
field = model_class._meta.get_field(key)
if not field:
continue
if isinstance(field, models.ManyToManyField):
# can't add m2m until parent is saved
continue
elif isinstance(field, models.ForeignKey) and hasattr(value, 'items'):
rel_instance = create_or_update_and_get(field.rel.to, value)
setattr(instance, key, rel_instance)
else:
setattr(instance, key, value)
instance.save()
# now add the m2m relations
for field in model_class._meta.many_to_many:
if field.name in data and hasattr(data[field.name], 'append'):
for obj in data[field.name]:
rel_instance = create_or_update_and_get(field.rel.to, obj)
getattr(instance, field.name).add(rel_instance)
return instance
# for example:
from django.utils.simplejson import simplejson as json
data = json.loads(ninja_json)
ninja = create_or_update_and_get(NinjaData, data)
发布于 2011-12-04 09:37:33
我不知道您是否熟悉这个术语,但您基本上要做的是将序列化/字符串格式(在本例中为JSON)反序列化为Python模型对象。
我不熟悉Python库,所以我不推荐/认可任何库,但是使用"python“、”反序列化“、" JSON”、"object“和”jsonpickle“等术语进行搜索,似乎会发现github上的some Django documentation for serialization和库jsonpickle。
发布于 2011-12-03 21:47:22
我实际上也有同样的需求,并且我编写了一个自定义数据库字段来处理它。只需将以下内容保存在项目的Python模块中(例如,适当应用程序中的fields.py
文件),然后导入并使用它:
class JSONField(models.TextField):
"""Specialized text field that holds JSON in the database, which is
represented within Python as (usually) a dictionary."""
__metaclass__ = models.SubfieldBase
def __init__(self, blank=True, default='{}', help_text='Specialized text field that holds JSON in the database, which is represented within Python as (usually) a dictionary.', *args, **kwargs):
super(JSONField, self).__init__(*args, blank=blank, default=default, help_text=help_text, **kwargs)
def get_prep_value(self, value):
if type(value) in (str, unicode) and len(value) == 0:
value = None
return json.dumps(value)
def formfield(self, form_class=JSONFormField, **kwargs):
return super(JSONField, self).formfield(form_class=form_class, **kwargs)
def bound_data(self, data, initial):
return json.dumps(data)
def to_python(self, value):
# lists, dicts, ints, and booleans are clearly fine as is
if type(value) not in (str, unicode):
return value
# empty strings were intended to be null
if len(value) == 0:
return None
# NaN should become null; Python doesn't have a NaN value
if value == 'NaN':
return None
# try to tell the difference between a "normal" string
# and serialized JSON
if value not in ('true', 'false', 'null') and (value[0] not in ('{', '[', '"') or value[-1] not in ('}', ']', '"')):
return value
# okay, this is a JSON-serialized string
return json.loads(value)
有几件事。首先,如果你使用South,你需要向它解释你的自定义字段是如何工作的:
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [r'^feedmagnet\.tools\.fields\.models\.JSONField'])
其次,虽然我做了很多工作来确保这个自定义字段在任何地方都很好用,比如在序列化格式和Python之间来回清晰地切换。有一个地方它不能很好地工作,那就是当它与manage.py dumpdata
结合使用时,它将Python合并成一个字符串,而不是将其转储到JSON中,这不是您想要的。我发现这在实际操作中是一个小问题。
有关writing custom model fields的更多文档。
我断言,这是实现这一目标的最好和最明显的方法。请注意,我还假设您不需要对此数据进行查找--例如,您将根据其他条件检索记录,这将随之而来。如果需要根据JSON中的内容进行查找,请确保它是一个真正的SQL字段(并确保它已被索引!)。
https://stackoverflow.com/questions/8367609
复制相似问题