如何在 Django 1.6 模型装置中使用某些字段而不是主键?
How to use some field instead of primary keys in Django 1.6 model fixtures?
我需要在不使用主键的情况下转储和加载模型对象的固定装置。模型是平面的。我知道 Django 中的自然键,花了很多时间阅读文档,但所有文档都有仅针对使用自然键而不是关系 (fk / m2m) 的解决方案。这完全不是我需要的。
我需要这样的东西:
(models.py)
class Template(models.Model):
name = models.CharField(_('name'), max_length=100)
content = models.TextField(_('content'), blank=True)
def natural_key(self):
return (self.name,)
(fixture1.json)
[
{
"pk": null,
"model": "dbtemplates.Template",
"fields" {
"content": "",
"name": "product"
}
}
]
在命令之后
./manage.py <SOME_LOADDATA_COMMAND> fixture1.json --natural
我需要更新名称为 "product" 的模板对象或将其插入。
标准的 Django 命令不会这样做。请帮我解决任何问题。也许有一些图书馆?我很困惑。
Django 1.6。 Python2.7
Django 1.6 不提供使用自然主键转储数据的方法,但 Django 1.7 提供。
- Django 1.6 文档:https://docs.djangoproject.com/en/1.7/ref/django-admin/#dumpdata-app-label-app-label-app-label-model
- Django 1.7 文档(注意
--natural-primary
选项):https://docs.djangoproject.com/en/1.7/ref/django-admin/#dumpdata-app-label-app-label-app-label-model
不幸的是,基本 Django 1.6 序列化程序不支持 use_natural_primary_keys
关键字参数:https://github.com/django/django/blob/1.6.11/django/core/serializers/base.py#L20
所以我建议您要么升级到 django 1.7(我完全理解这并不总是可行的),要么您编写自己的序列化程序,从基础 Django 1.7 序列化程序 (https://github.com/django/django/blob/1.7.11/django/core/serializers/base.py) 中汲取灵感。
基于 answer of régis-b 我写了一些代码,允许在 Django 1.6 "loaddata" 管理命令 中使用自然键,而无需升级到 1.7。我选择这种方式是因为对我的项目进行全面升级可能会很痛苦。这个方案可以算是临时的。
树结构:
├── project_main_app
│ ├── __init__.py
│ ├── backports
│ │ ├── __init__.py
│ │ └── django
│ │ ├── __init__.py
│ │ └── deserializer.py
│ └── monkey.py
project_main_app/backports/django/deserializer.py
from __future__ import unicode_literals
from django.conf import settings
from django.core.serializers import base
from django.core.serializers.python import _get_model
from django.db import models, DEFAULT_DB_ALIAS
from django.utils.encoding import smart_text
from django.utils import six
def Deserializer(object_list, **options):
"""
Deserialize simple Python objects back into Django ORM instances.
It's expected that you pass the Python objects themselves (instead of a
stream or a string) to the constructor
"""
db = options.pop('using', DEFAULT_DB_ALIAS)
ignore = options.pop('ignorenonexistent', False)
models.get_apps()
for d in object_list:
# Look up the model and starting build a dict of data for it.
Model = _get_model(d["model"])
data = {Model._meta.pk.attname: Model._meta.pk.to_python(d.get("pk", None))}
m2m_data = {}
model_fields = Model._meta.get_all_field_names()
# Handle each field
for (field_name, field_value) in six.iteritems(d["fields"]):
if ignore and field_name not in model_fields:
# skip fields no longer on model
continue
if isinstance(field_value, str):
field_value = smart_text(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True)
field = Model._meta.get_field(field_name)
# Handle M2M relations
if field.rel and isinstance(field.rel, models.ManyToManyRel):
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
def m2m_convert(value):
if hasattr(value, '__iter__') and not isinstance(value, six.text_type):
return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk
else:
return smart_text(field.rel.to._meta.pk.to_python(value))
else:
m2m_convert = lambda v: smart_text(field.rel.to._meta.pk.to_python(v))
m2m_data[field.name] = [m2m_convert(pk) for pk in field_value]
# Handle FK fields
elif field.rel and isinstance(field.rel, models.ManyToOneRel):
if field_value is not None:
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
if hasattr(field_value, '__iter__') and not isinstance(field_value, six.text_type):
obj = field.rel.to._default_manager.db_manager(db).get_by_natural_key(*field_value)
value = getattr(obj, field.rel.field_name)
# If this is a natural foreign key to an object that
# has a FK/O2O as the foreign key, use the FK value
if field.rel.to._meta.pk.rel:
value = value.pk
else:
value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
data[field.attname] = value
else:
data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
else:
data[field.attname] = None
# Handle all other fields
else:
data[field.name] = field.to_python(field_value)
# The key block taken from Django 1.7 sources
obj = build_instance(Model, data, db)
yield base.DeserializedObject(obj, m2m_data)
# This is also taken from Django 1.7 sources
def build_instance(Model, data, db):
"""
Build a model instance.
If the model instance doesn't have a primary key and the model supports
natural keys, try to retrieve it from the database.
"""
obj = Model(**data)
if (obj.pk is None and hasattr(Model, 'natural_key') and
hasattr(Model._default_manager, 'get_by_natural_key')):
natural_key = obj.natural_key()
try:
obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
except Model.DoesNotExist:
pass
return obj
project_main_app/monkey.py
def patch_all():
import django.core.serializers.python
import project_main_app.backports.django.deserializer
# Patch the Deserializer
django.core.serializers.python.Deserializer = project_main_app.backports.django.deserializer.Deserializer
project_main_app/init.py
from project_main_app.monkey import patch_all
patch_all()
所以在这之后我只添加了一些东西,我的模型就变成了
class TemplateManager(models.Manager):
"""1"""
def get_by_natural_key(self, name):
return self.get(name=name)
class Template(models.Model):
name = models.CharField(_('name'), max_length=100)
content = models.TextField(_('content'), blank=True)
objects = TemplateManager() # 2
def natural_key(self):
"""3"""
return (self.name,)
如果 fixtures 有一个空的 pk,比如
[
{
"pk": null,
"model": "dbtemplates.Template",
"fields": {
"content": "Some content",
"name": "product"
}
}
]
标准命令 ./manage.py loaddata dbtemplates.Template 更新或插入与 name 字段匹配的对象。
警告:所有自然键组件(如我的 "name")在数据库中必须具有唯一值。正确的方法是在定义模型时通过添加参数 "unique=True" 来设置它们的唯一性。
我需要在不使用主键的情况下转储和加载模型对象的固定装置。模型是平面的。我知道 Django 中的自然键,花了很多时间阅读文档,但所有文档都有仅针对使用自然键而不是关系 (fk / m2m) 的解决方案。这完全不是我需要的。
我需要这样的东西:
(models.py)
class Template(models.Model):
name = models.CharField(_('name'), max_length=100)
content = models.TextField(_('content'), blank=True)
def natural_key(self):
return (self.name,)
(fixture1.json)
[
{
"pk": null,
"model": "dbtemplates.Template",
"fields" {
"content": "",
"name": "product"
}
}
]
在命令之后
./manage.py <SOME_LOADDATA_COMMAND> fixture1.json --natural
我需要更新名称为 "product" 的模板对象或将其插入。
标准的 Django 命令不会这样做。请帮我解决任何问题。也许有一些图书馆?我很困惑。
Django 1.6。 Python2.7
Django 1.6 不提供使用自然主键转储数据的方法,但 Django 1.7 提供。
- Django 1.6 文档:https://docs.djangoproject.com/en/1.7/ref/django-admin/#dumpdata-app-label-app-label-app-label-model
- Django 1.7 文档(注意
--natural-primary
选项):https://docs.djangoproject.com/en/1.7/ref/django-admin/#dumpdata-app-label-app-label-app-label-model
不幸的是,基本 Django 1.6 序列化程序不支持 use_natural_primary_keys
关键字参数:https://github.com/django/django/blob/1.6.11/django/core/serializers/base.py#L20
所以我建议您要么升级到 django 1.7(我完全理解这并不总是可行的),要么您编写自己的序列化程序,从基础 Django 1.7 序列化程序 (https://github.com/django/django/blob/1.7.11/django/core/serializers/base.py) 中汲取灵感。
基于 answer of régis-b 我写了一些代码,允许在 Django 1.6 "loaddata" 管理命令 中使用自然键,而无需升级到 1.7。我选择这种方式是因为对我的项目进行全面升级可能会很痛苦。这个方案可以算是临时的。
树结构:
├── project_main_app
│ ├── __init__.py
│ ├── backports
│ │ ├── __init__.py
│ │ └── django
│ │ ├── __init__.py
│ │ └── deserializer.py
│ └── monkey.py
project_main_app/backports/django/deserializer.py
from __future__ import unicode_literals
from django.conf import settings
from django.core.serializers import base
from django.core.serializers.python import _get_model
from django.db import models, DEFAULT_DB_ALIAS
from django.utils.encoding import smart_text
from django.utils import six
def Deserializer(object_list, **options):
"""
Deserialize simple Python objects back into Django ORM instances.
It's expected that you pass the Python objects themselves (instead of a
stream or a string) to the constructor
"""
db = options.pop('using', DEFAULT_DB_ALIAS)
ignore = options.pop('ignorenonexistent', False)
models.get_apps()
for d in object_list:
# Look up the model and starting build a dict of data for it.
Model = _get_model(d["model"])
data = {Model._meta.pk.attname: Model._meta.pk.to_python(d.get("pk", None))}
m2m_data = {}
model_fields = Model._meta.get_all_field_names()
# Handle each field
for (field_name, field_value) in six.iteritems(d["fields"]):
if ignore and field_name not in model_fields:
# skip fields no longer on model
continue
if isinstance(field_value, str):
field_value = smart_text(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True)
field = Model._meta.get_field(field_name)
# Handle M2M relations
if field.rel and isinstance(field.rel, models.ManyToManyRel):
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
def m2m_convert(value):
if hasattr(value, '__iter__') and not isinstance(value, six.text_type):
return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk
else:
return smart_text(field.rel.to._meta.pk.to_python(value))
else:
m2m_convert = lambda v: smart_text(field.rel.to._meta.pk.to_python(v))
m2m_data[field.name] = [m2m_convert(pk) for pk in field_value]
# Handle FK fields
elif field.rel and isinstance(field.rel, models.ManyToOneRel):
if field_value is not None:
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
if hasattr(field_value, '__iter__') and not isinstance(field_value, six.text_type):
obj = field.rel.to._default_manager.db_manager(db).get_by_natural_key(*field_value)
value = getattr(obj, field.rel.field_name)
# If this is a natural foreign key to an object that
# has a FK/O2O as the foreign key, use the FK value
if field.rel.to._meta.pk.rel:
value = value.pk
else:
value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
data[field.attname] = value
else:
data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
else:
data[field.attname] = None
# Handle all other fields
else:
data[field.name] = field.to_python(field_value)
# The key block taken from Django 1.7 sources
obj = build_instance(Model, data, db)
yield base.DeserializedObject(obj, m2m_data)
# This is also taken from Django 1.7 sources
def build_instance(Model, data, db):
"""
Build a model instance.
If the model instance doesn't have a primary key and the model supports
natural keys, try to retrieve it from the database.
"""
obj = Model(**data)
if (obj.pk is None and hasattr(Model, 'natural_key') and
hasattr(Model._default_manager, 'get_by_natural_key')):
natural_key = obj.natural_key()
try:
obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
except Model.DoesNotExist:
pass
return obj
project_main_app/monkey.py
def patch_all():
import django.core.serializers.python
import project_main_app.backports.django.deserializer
# Patch the Deserializer
django.core.serializers.python.Deserializer = project_main_app.backports.django.deserializer.Deserializer
project_main_app/init.py
from project_main_app.monkey import patch_all
patch_all()
所以在这之后我只添加了一些东西,我的模型就变成了
class TemplateManager(models.Manager):
"""1"""
def get_by_natural_key(self, name):
return self.get(name=name)
class Template(models.Model):
name = models.CharField(_('name'), max_length=100)
content = models.TextField(_('content'), blank=True)
objects = TemplateManager() # 2
def natural_key(self):
"""3"""
return (self.name,)
如果 fixtures 有一个空的 pk,比如
[
{
"pk": null,
"model": "dbtemplates.Template",
"fields": {
"content": "Some content",
"name": "product"
}
}
]
标准命令 ./manage.py loaddata dbtemplates.Template 更新或插入与 name 字段匹配的对象。
警告:所有自然键组件(如我的 "name")在数据库中必须具有唯一值。正确的方法是在定义模型时通过添加参数 "unique=True" 来设置它们的唯一性。