【问题标题】:Excluding primary key in Django dumpdata with natural keys使用自然键排除 Django 转储数据中的主键
【发布时间】:2012-03-15 06:27:53
【问题描述】:

启用自然键后,如何从 Django 的转储数据生成的 JSON 中排除主键?

我已经构建了一条我想“导出”的记录,以便其他人可以将其用作模板,方法是将其加载到具有相同架构的单独数据库中,而不会与同一模型中的其他记录发生冲突。

据我了解,Django 支持自然键,这似乎是 NK 的设计初衷。我的记录有一个唯一的name 字段,也用作自然键。

所以当我跑步时:

from django.core import serializers
from myapp.models import MyModel
obj = MyModel.objects.get(id=123)
serializers.serialize('json', [obj], indent=4, use_natural_keys=True)

我希望输出类似于:

[
    {
        "model": "myapp.mymodel", 
        "fields": {
            "name": "foo", 
            "create_date": "2011-09-22 12:00:00", 
            "create_user": [
                "someusername"
            ]
        }
    }
]

然后我可以使用 loaddata 将其加载到另一个数据库中,并期望它被动态分配一个新的主键。请注意,我的“create_user”字段是 Django 的 auth.User 模型的 FK,它支持自然键,并且它作为其自然键而不是整数主键输出。

但是,实际上生成的是:

[
    {
        "pk": 123,
        "model": "myapp.mymodel", 
        "fields": {
            "name": "foo", 
            "create_date": "2011-09-22 12:00:00", 
            "create_user": [
                "someusername"
            ]
        }
    }
]

这将明显与主键 123 的任何现有记录冲突并覆盖。

解决此问题的最佳方法是什么?我不想将所有自动生成的主键整数字段追溯更改为等效的自然键,因为这会导致性能下降以及劳动密集型。

编辑:这似乎是 a bugreported...2 年前...并且在很大程度上被忽略了...

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

    为 2018 年及以后遇到此问题的任何人更新答案。

    有一种方法可以通过使用自然键和 unique_together 方法来省略主键。取自Django documentation on serialization

    您可以使用此命令进行测试:

    python manage.py dumpdata app.model --pks 1,2,3 --indent 4 --natural-primary --natural-foreign > dumpdata.json ;
    

    自然键的序列化

    那么,如何让 Django 在序列化对象时发出自然键?首先,您需要添加另一个方法——这次是添加到模型本身:

    class Person(models.Model):
        objects = PersonManager()
    
        first_name = models.CharField(max_length=100)
        last_name = models.CharField(max_length=100)
        birthdate = models.DateField()
    
        def natural_key(self):
            return (self.first_name, self.last_name)
    
        class Meta:
            unique_together = (('first_name', 'last_name'),)
    

    那个方法应该总是返回一个自然键元组——在这个例子中,(名字,姓氏)。然后,当您调用serializers.serialize() 时,您提供use_natural_foreign_keys=Trueuse_natural_primary_keys=True 参数:

    serializers.serialize('json', [book1, book2], indent=2, use_natural_foreign_keys=True, use_natural_primary_keys=True)
    

    当指定use_natural_foreign_keys=True 时,Django 将使用natural_key() 方法序列化对定义该方法类型的对象的任何外键引用。

    当指定use_natural_primary_keys=True时,Django不会在这个对象的序列化数据中提供主键,因为它可以在反序列化时计算出来:

        {
            "model": "store.person",
            "fields": {
                "first_name": "Douglas",
                "last_name": "Adams",
                "birth_date": "1952-03-11",
            }
        }
    

    【讨论】:

      【解决方案2】:

      json 的问题在于您不能省略 pk 字段,因为再次加载夹具数据时需要它。如果不存在,json 将失败并显示

      $ python manage.py loaddata some_data.json
      [...]
      File ".../django/core/serializers/python.py", line 85, in Deserializer
      data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
      KeyError: 'pk'
      

      正如this question 的回答中指出的那样,如果您真的想省略pk 属性,可以使用yamlxml只需将主键值替换为null.

      import re
      from django.core import serializers
      
      some_objects = MyClass.objects.all()
      s = serializers.serialize('json', some_objects, use_natural_keys=True)
      # Replace id values with null - adjust the regex to your needs
      s = re.sub('"pk": [0-9]{1,5}', '"pk": null', s)
      

      【讨论】:

      • 这正是我自己发现的解决方案。
      • 看来你现在可以在json中省略pk字段了。
      【解决方案3】:

      在单独的模块中覆盖Serializer 类:

      from django.core.serializers.json import Serializer as JsonSerializer
      
      class Serializer(JsonSerializer):
      
          def end_object(self, obj):
              self.objects.append({
                  "model"  : smart_unicode(obj._meta),
                  "fields" : self._current, 
                  # Original method adds the pk here
              })
              self._current = None
      

      在 Django 中注册:

      serializers.register_serializer("json_no_pk", "path.to.module.with.custom.serializer")
      

      添加使用它:

      serializers.serialize('json_no_pk', [obj], indent=4, use_natural_keys=True)
      

      【讨论】:

      • 我更喜欢这个解决方案,但是没有 pk 值反序列化会失败。您应该设置 "pk": None, 而不是删除整个字段
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-01
      • 2018-10-22
      • 2011-12-31
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      相关资源
      最近更新 更多