如何在Django 1.6模型夹具中使用一些字段而不是主键?

时间:2016-08-18 06:40:42

标签: python django django-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

我需要更新名为&#34; product&#34;的模板对象。或者插入它。

标准的Django命令不会这样做。请帮助我解决任何问题。也许这有一些库?我很困惑。

Django 1.6。 Python 2.7

2 个答案:

答案 0 :(得分:3)

Django 1.6没有提供使用自然主键转储数据的方法,但是Django 1.7可以。

不幸的是,基本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)中汲取灵感。

答案 1 :(得分:0)

基于answer of régis-b我编写了一些允许在Django 1.6&#34; loaddata&#34;中使用自然键的代码。管理命令,无需升级到1.7 。我选择这种方式是因为我的项目的全面升级可能会很痛苦。这个解决方案可以被认为是暂时的。

树形结构:

├── project_main_app
│   ├── __init__.py
│   ├── backports
│   │   ├── __init__.py
│   │   └── django
│   │       ├── __init__.py
│   │       └── deserializer.py
│   └── monkey.py

<强> project_main_app /反向移植/ 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,)

如果灯具有像

那样的空白pk
[
{
    "pk": null,
    "model": "dbtemplates.Template",
    "fields": {
        "content": "Some content",
        "name": "product"
    }
}
]

标准命令 ./ manage.py loaddata dbtemplates.Template 通过名称字段更新或插入对象匹配。

警告:所有自然关键组件(例如&#34;名称&#34;在我的情况下)都必须在数据库中具有唯一值。正确的方法是通过添加参数&#34; unique = True&#34;来设置它们的唯一性。在定义模型时。