English 中文(简体)
在一个干,的习俗领域,让施工者获得一类投入参数是否无效?
原标题:In a django custom field, is it invalid to give the constructor a class-type input parameter?
The bounty expires in 4 days. Answers to this question are eligible for a +150 reputation bounty. Ziv wants to draw more attention to this question.

我试图在达詹戈确定一个习俗领域,我希望用一个助手来开始。 外地班级接受助手的直径,并在内部储存。 想这样做:

# define helper class
class Foo:
   # ...

# instance of helper class
myFoo = Foo()

# model with field accepting helper class
class MyModel(models.Model):
    foo = FooManagedField(foo=myFoo)

This seems to me like a pretty reasonable thing to do.
But in practice, it completely breaks migration tooling.

我看到的是: 一旦任何模型都利用了这个领域,Django就认为它总是处于变化状态(我假定它不考虑不同的物体)。 因此,它登记的变化总是需要新的移民。

Is there any way to solve this, any way to indicate that the definition is consistent? Or is it a blanket restriction that a custom field can only be initialized with primitives?
Or maybe there s a reason I m not seeing that passing object instances into a custom Field implementation is a bad idea and I shouldn t want to do it?

Walkthrough and Detail

I m 界定一个简单的习俗Django领域如下:

class FooManagedField(CharField):
    def __init__(self, foo : Foo, *args, **kwargs) -> None:
        self.foo : Foo = foo
        super().__init__(*args, **kwargs)
    
    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs["foo"]=self.foo
        return name, path, args, kwargs

<代码>Foo为简单目标,例如:

class Foo:
    def __init__(self, n):
        self.n=n
    def __repr__(self):
        return f"Foo({self.n})"

现在,我把这一新的领域添加到一种模式中(为支持移徙提供书面支持类别<代码>FooSerializer——以下支持代码):

from django.db import models
from .utils.foo_field import Foo, FooManagedField

fooA = Foo(1)
fooB = Foo(2)

class MyModel(models.Model):
    n1 = models.IntegerField() # preexisting field
    n2 = models.IntegerField() # preexisting field
    foo1 = FooManagedField(foo=fooA) # new FooManagedField
    foo2 = FooManagedField(foo=fooB) # new FooManagedField

运行<条码> 移民按预期会产生移民档案:

    operations = [
        migrations.AddField(
            model_name= mymodel ,
            name= foo1 ,
            field=myproject.utils.foo_field.FooManagedField(foo=Foo(1)),
        ),
        migrations.AddField(
            model_name= mymodel ,
            name= foo2 ,
            field=myproject.utils.foo_field.FooManagedField(foo=Foo(2)),
        ),
    ]

但随后,运行<条码>,再制作>>>>,再制作another migration文档。 再开车将产生三分之一。 根据这一实地定义,没有最终情况。 所有新的移民都将如此:

    operations = [
        migrations.AlterField(
            model_name= mymodel ,
            name= foo1 ,
            field=myproject.utils.foo_field.FooManagedField(foo=Foo(1)),
        ),
        migrations.AlterField(
            model_name= mymodel ,
            name= foo2 ,
            field=myproject.utils.foo_field.FooManagedField(foo=Foo(2)),
        ),
    ]

Question

这在Django是否是一种硬性限制? 是否有办法提供同我一样的阶级?

而且,我是否希望从这一建筑开始,这是我在法典设计中犯错误的标志吗? 我以错误的方式来到这里——难道不宜把一些可改观的逻辑归纳为界定实地的助手类别?

References

  • Infinite AlterField Migrations due to default callable object mismatch is an existing ticket which helped me figure out what the immediate issue was (though I m still missing the rationale and a workaround, or else to understand why this shouldn t be worked around)
  • non_db_attrs is a relatively new property which lets you mark "attributes of a field that don’t affect a column definition". Is this helpful in any way to this situation? I tried adding this and saw no improvement, but maybe I misunderstood usage.

Supporting code

Foo序列izer(参考here):

from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter

class FooSerializer(BaseSerializer):
    def serialize(self):
        return repr(self.value), {"from myproject.utils.foo_field import Foo"}


MigrationWriter.register_serializer(Foo, FooSerializer)
问题回答

To answer your main question: The limitation you re encountering in Django is not a hard limitation in the sense that you cannot provide a class as a default value for a field. However, it s a limitation in the way Django s migration system handles default values.

如果你想要保留<代码>default_foo的功能,你可以修改你的示范实地定义,将“lambda”功能用作提供<代码>defoo_foo必要论点的默认价值。

class FooField(CharField):
    def __init__(self, *args, **kwargs) -> None:
        default = kwargs.get("default", default_foo())
        kwargs["default"] = default_foo_callable(default)
        super().__init__(*args, **kwargs)
    
    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        if kwargs.get("default") == default_foo_callable():
            del kwargs["default"]
        return name, path, args, kwargs

def default_foo_callable(value=0):
    return lambda: default_foo(value)

def default_foo(n):
    return Foo(n)

修改你的模型定义,以使用可呼吁的违约价值:

class MyModel(models.Model):
    n1 = models.IntegerField()
    n2 = models.IntegerField()
    foo1 = FooField(default=default_foo_callable(1))
    foo2 = FooField(default=default_foo_callable(2))

处理这种情况的简单方法是在<<>条码>foo上设定一个静态数值。 方法

class FooManagedField(models.CharField):
    def __init__(self, foo: Foo, *args, **kwargs) -> None:
        self.foo: Foo = foo
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs["foo"] = "Foo"  # set a static value
        return name, path, args, kwargs

也可使用non_db_attrs,

class FooManagedField(models.CharField):
    non_db_attrs = models.CharField.non_db_attrs + ("foo",)

    def __init__(self, foo: Foo, *args, **kwargs) -> None:
        self.foo: Foo = foo
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs["foo"] = str(self.foo) # this is more dynamic now, but look at the `non_db_attrs` attribute
        return name, path, args, kwargs




相关问题
How to get two random records with Django

How do I get two distinct random records using Django? I ve seen questions about how to get one but I need to get two random records and they must differ.

Moving (very old) Zope/Plone Site to Django

I am ask to move data from a (now offline) site driven by Plone to a new Django site. These are the version informations I have: Zope Version (unreleased version, python 2.1.3 ) Python Version 2.1....

Can Django models use MySQL functions?

Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like ...

Flexible pagination in Django

I d like to implement pagination such that I can allow the user to choose the number of records per page such as 10, 25, 50 etc. How should I go about this? Is there an app I can add onto my project ...

is it convenient to urlencode all next parameters? - django

While writing code, it is pretty common to request a page with an appended "next" query string argument. For instance, in the following template code next points back to the page the user is on: &...

Pragmatically adding give-aways/freebies to an online store

Our business currently has an online store and recently we ve been offering free specials to our customers. Right now, we simply display the special and give the buyer a notice stating we will add the ...

热门标签