简体   繁体   English

如何在子类化 django 的 model FileField 时使用关键字 arguments

[英]How can I use keyword arguments when subclassing django's model FileField

I'm trying to subclass Django's models.FileField but somewhere, the instantiation is going very wrong.我正在尝试对 Django 的models.FileField进行子类化,但在某个地方,实例化非常错误。

Here's my code:这是我的代码:

class DatafileObjectField(models.FileField):

    def __init__(self, source_key=None, **kwargs):
        print('SOURCE KEY IS:', source_key)
        source = settings.DATA_SOURCES[source_key]
        kwargs["storage"] = import_string(source["storage"])(**source["storage_settings"])
        super().__init__(**kwargs)


class MyModel(models.Model):

    # THIS IS THE ONLY REFERENCE IN MY ENTIRE CODEBASE TO THIS FIELD
    # Yet somehow the field is instantiated by django without the source_key argument
    file = DatafileObjectField(source_key='my-data-source', help_text="Upload a data file containing mast timeseries")

When I do:当我做:

python mange.py makemigrations

That print statement is issued twice:该打印语句发出两次:

SOURCE KEY IS: my-data-source
<output from checks>
SOURCE KEY IS: None

Followed by the inevitable error, because the second instantiation of the subclassed DatafileObjectField doesn't receive the source_key argument.其次是不可避免的错误,因为子类 DatafileObjectField 的第二次实例化没有收到source_key参数。

Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 85, in wrapped
    res = handle_func(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/commands/makemigrations.py", line 142, in handle
    ProjectState.from_apps(apps),
  File "/usr/local/lib/python3.8/site-packages/django/db/migrations/state.py", line 220, in from_apps
    model_state = ModelState.from_model(model)
  File "/usr/local/lib/python3.8/site-packages/django/db/migrations/state.py", line 409, in from_model
    fields.append((name, field.clone()))
  File "/usr/local/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 514, in clone
    return self.__class__(*args, **kwargs)
  File "/app/backend/datalake/models/mast_timeseries.py", line 52, in __init__
    source = settings.DATA_SOURCES[source_key]
KeyError: None

What on earth is django doing? django到底在做什么? Why isn't source_key always passed?为什么source_key不总是通过?

A Model / model field needs to be serializable to be able to use it in the migrations system. Model / model 字段需要可序列化才能在迁移系统中使用。 Django uses the deconstruct method on classes to make them serializable. Django 对类使用deconstruct方法使它们可序列化。 This method according to the documentation :此方法根据文档

Returns a 4-tuple with enough information to recreate the field:返回一个 4 元组,其中包含足够的信息来重新创建该字段:

  1. The name of the field on the model. model 上的字段名称。
  2. The import path of the field (eg "django.db.models.IntegerField").字段的导入路径(例如“django.db.models.IntegerField”)。 This should be the most portable version, so less specific may be better.这应该是最便携的版本,所以不太具体可能会更好。
  3. A list of positional arguments.位置 arguments 的列表。
  4. A dict of keyword arguments.关键字 arguments 的字典。

Hence you need to override this method and pass your own kwarg to it so that the field can be serialized:因此,您需要重写此方法并将您自己的 kwarg 传递给它,以便可以序列化该字段:

class DatafileObjectField(models.FileField):

    def __init__(self, source_key=None, **kwargs):
        print('SOURCE KEY IS:', source_key)
        source = settings.DATA_SOURCES[source_key]
        kwargs["storage"] = import_string(source["storage"])(**source["storage_settings"])
        super().__init__(**kwargs)
    
    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs['source_key'] = get_source_key_from_storage(self.storage) # Somehow get source key here?
        return name, path, args, kwargs

Note : I use some non-existent function get_source_key_from_storage since you haven't set it on the instance of the field.注意:我使用了一些不存在的 function get_source_key_from_storage因为您没有在该字段的实例上设置它。 You can get the source key somehow or set it on the instance and use that here.您可以以某种方式获取源密钥或将其设置在实例上并在此处使用。

OK, figured it out.好的,想通了。 I'd seen the mention of the deconstruct method in django docs but not fully absorbed its purpose.我在django 文档中看到了deconstruct方法的提及,但没有完全理解它的目的。

Basically, the Field is instantiated (correctly, that's the first print output in the question) then its deconstruct method is used by makemigrations to determine what pieces of information need to be saved in the migration .基本上, Field 被实例化(正确地,这是问题中的第一个打印 output )然后makemigrations使用它的解构方法来确定需要在迁移中保存哪些信息

It tells the migration what's important to actually store.它告诉迁移实际存储什么是重要的。

So in my case I needed to add the storage_key kwarg into the values returned from the deconstruct() method (which meant saving it on the model).因此,在我的情况下,我需要storage_key kwarg 添加到 deconstruct deconstruct()方法返回的值中(这意味着将其保存在模型上)。

In this particular use case, because I'm determining what storage class to use on instantiation, I don't also need to serialise the storage kwarg, so I delete that.在这个特定的用例中,因为我正在确定在实例化时使用什么存储 class,所以我也不需要序列化storage kwarg,所以我删除了它。

Working solution:工作解决方案:


class DatafileObjectField(models.FileField):

    def __init__(self, source_key=None, **kwargs):
        self.source_key = source_key
        source = settings.OCTUE_DATA_SOURCES[source_key]
        kwargs["storage"] = import_string(source["storage"])(**source["storage_settings"])
        super().__init__(**kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        # Make sure the migration knows that this settings key is required for instantiation
        kwargs["source_key"] = self.source_key
        # Remove this key, which is no longer required (calculated during __init__)
        kwargs.pop("storage")
        return name, path, args, kwargs

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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