简体   繁体   中英

How does Django loaddata know which fields make the natural key?

I am using Django's dumpdata to save data and loaddata to reload it. I am also using natural keys. My model looks similar to this:

class LinkManager(models.Manager):
    def get_by_natural_key(self, url):
        return self.get(url=url)

class Link(models.Model):
    objects = LinkManager()
    title = models.CharField(max_length=200)
    url = models.URLField()

    def natural_key(self):
        return (self.url, )

If I export and reimport the data, Django recognizes that the objects already exist and doesn't create duplicates. If I change the title, it correctly updates the objects. However, if I change the URL, it correctly treats it as a new object - although I forgot to mark url unique! How does it guess my intent?

How does django know that my url field is the natural key? There is no get_natural_fields function. Django could call natural_key on the class instead of an instance to get the fields, but that seems really brittle:

>>> [f.field_name for f in Link.natural_key(Link)]
['url']

The reason I want to know this is that I am writing my own special importer (to replace my use of loaddata), and I would like to take advantage of natural keys without hardcoding the natural key (or the "identifying" fields) for each model. Currently, I "identify" an object by it's unique fields - I do:

obj, created = Model.objects.update_or_create(**identifying, defaults=other)

but Django seems to be choosing it's "identifying" fields differently.

I think I've found it out. Django does not just call get_by_natural_key , it first calls natural_key . How does it do that, if it doesn't have an instance of the model?

It simply creates an instance, not backed by the database, from the constructor (d'oh!): Model(**data) . See build_instance in django.core.serializers.base . Then it calls natural_key on the newly created object, and immediately get_by_natural_key to retrive the pk that belongs to the object, if present in the database. This way, Django does not need to know what fields the natural key depends on, it just needs to know how to get it from data. You can just call save() on the retrieved instance, if it is in the database it will have a pk and will update, if not it will create a new row.

Source of the build_instance function (Django 1.11.2):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM