如何从插件中修补南方处理的模型?

Posted

技术标签:

【中文标题】如何从插件中修补南方处理的模型?【英文标题】:How to monkey patch south handled models from plugin? 【发布时间】:2013-05-18 14:23:09 【问题描述】:

我正在制作一个带有插件的 django 网站。每个插件都是一个简单的 django 应用程序,取决于一个主要的(甚至是其他插件)。

虽然我很清楚应用程序/插件之间的依赖关系,但通过猴子补丁添加列(作为插件特定模型的外键)以避免主应用程序依赖插件应该是可以接受的。

由于主应用程序已经有一个南管理,所有插件也有,我无法在这些模块的设置中更改迁移目录。

那么,我如何从其他 South 应用程序中修补一个 South 应用程序模型?

ps:我是法国人,如果您发现任何错误,请随时纠正我的问题,或者如果我不清楚,请提出任何问题。

编辑:我添加了一个关于我现在如何处理 django 迁移的答案。

【问题讨论】:

通过 monkeypatch 更改表结构恕我直言,这是一个非常非常糟糕的主意。您可能希望使用 GenericForeignKeys 或任何其他临时“非破坏性”解决方案。 它更高效,不是那么难看,而且我是所有这些应用程序的开发人员(因此没有无人看管的冲突风险)。即使我从未使用过 GenericForeignKeys 但我不喜欢它们。 我不明白这个问题。适当的猴子修补会让它在南方不显眼。您只需要添加一个递归迁移脚本。 在哪里?什么叫递归迁移脚本? 【参考方案1】:

目前我最好的解决方案是在插件中制作自己的迁移文件(这意味着在迁移文件的模型字典中添加表)。

如果所有模型都将自动跟随,我将在下次迁移时看到。

在我的新迁移文件中:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.add_column(u'remoteapp_model', 'fieldname',
                      self.gf('django.db.models.fields.related.ForeignKey',
                      (to=orm["my_plugin.MyModel"], default=None, null=True, blank=True),
                      keep_default=False)


    def backwards(self, orm):
        db.delete_column(u'remoteapp_model', 'fieldname')

    # for models, you may want to copy from a previous migration file
    # and add from the models of the main application the related tables
    models =  

在我的模型文件中:

from remoteapp.models import RemoteModel
from django.db import models

class MyModel(models.Model):
    pass

models.ForeignKey(MyModel, null=True, blank=True,
                  default=None).contribute_to_class(RemoteModel, 'my_model')

【讨论】:

【参考方案2】:

看在上帝的份上,不要为此使用猴子补丁。使用继承,这就是它的用途。

只需让需要处理更多字段的插件扩展现有模型,然后使用多种技术中的一种来获取模型的最专业的类实例。为了实现这一点,我将下面的类用作所有将以这种方式使用的类的 mixin。

class Specializable(object):

    @classmethod
    def all_classes(selfclass):
        """ Returns the class this is called on, plus its known subclasses """
        subs = selfclass.__subclasses__()
        subs.insert(0, selfclass)
        return subs

    def get_sub_instance(self):
        """ Gets concrete instance of object of deepest subtype which has its ancestor link pointing to this object (depth first search behaviour). """
        selftype = type(self)
        for klass in selftype.__subclasses__():
            this_ptr_name = klass._meta.get_ancestor_link(selftype).name
            try:
                sub = klass.objects.get(**this_ptr_name: self)
                subsub = sub.get_sub_instance()
                if subsub: return subsub
                else: return sub
            except ObjectDoesNotExist:
                pass

    @classmethod
    def new_with_translator(selfclazz, name):
        def new(cls, *args, **kwargs):
            selfclazz.create_subclass_translator(cls, install = name)
            return models.Model.__new__(cls, *args, **kwargs)

        return new

    @classmethod
    def create_subclass_translator(selfclazz, Baseclass, install=None):
        """ Creates a classmethod object for installation on Baseclass,
        which acts as a factory for instances of subclasses of Baseclass,
        when called on that subclass. The factory takes as input an instance
        of a subclass (old_instance), and a dictionary of properties with which
        to initialise the new instance. It also installs the base class instance
        of old_instance as the base instance for the new instance. All three will
        share the same pk.

        if install is set, this will also install the function on Baseclass under
        that name if it does not have a property of that name. """

        def create_from_other_instance(selfclass, old_instance, properties):
            """ Creates an instance of this class using properties and old_instance.
            In particular, it will try to re-use the superclass instance of old_instance.
            properties should contain all of the fields for this class (but need not include the superclass values)
            This *must* be called on a subclass of the baseclass - it will give odd results if called on the baseclass itself.
            This will NOT delete old_instance and it will NOT SAVE the object created - the caller is responsible for
            those things."""

            if selfclass is Baseclass: raise TypeError("This method cannot be used on the base class")

            ancestor_link = selfclass._meta.get_ancestor_link(Baseclass).name
            properties.update(ancestor_link: getattr(old_instance,ancestor_link))
            for f in get_model_fields(Baseclass):
                val = getattr(old_instance, f)
                if val and not properties.get(f):
                    properties[f] = val

            return selfclass(**properties)

        new_method = classmethod(create_from_other_instance)

        if install and not hasattr(Baseclass, install):
            setattr(Baseclass, install, new_method)

        return new_method

这是它的使用示例,来自我的一些代码:

#KYCable inherits from Model, so must precede Model
class Director(KYCable, models.Model, Specializable, DateFormatter, AdminURL, Supercedable):
    def get_individual(self):
        return self.get_sub_instance().get_individual()

如您所见,Specializable 类需要注意它们的方法可能会被子类覆盖,并进行适当的编码。

如果您的插件知道子类关系,那么它可以使用一些技巧,例如在它知道的子类中搜索相同的模型 id,以在出现超类实例时获取相应的子类模型实例。

【讨论】:

它创建了一个新表,需要连接,降低性能,不太实用,需要更多代码......你没有说服我“猴子补丁很丑”,你没有给出任何论据。但是你得到了赏金,因为你是唯一一个回答的人。您阅读了问题并回答了您想回答的问题,而不是我问的问题。但是感谢您的回答,它的结构很好。 @christophe31 它更加实用,因为您使用的是应该使用的工具,因此避免引入难以发现的错误,以及避免需要手写你自己的迁移。我没有责任告诉你如何做一些可怕的事情。 Monkeypatching 用于动态对象系统。一旦将其映射到关系数据库,您就拥有了一个由动态对象系统在内存中表示的静态类系统。另外,您是否考虑过如何处理添加相同字段名称的两个插件?继承再次避免了这个问题。 @christophe31 至于你关于性能的观点 - 过早的优化是万恶之源。 @christophe31 另外,你还没有颁发赏金。【参考方案3】:

我在迁移到 django 1.7 和 django 迁移时发布了一个新答案,解决方案并不明显,我必须创建自己的迁移类来将外键添加到远程表。

from django.db.migrations import AddField

class AddRemoteField(AddField):
    def __init__(self, remote_app, *args, **kwargs):
        super(AddRemoteField, self).__init__(*args, **kwargs)
        self.remote_app = remote_app

    def state_forwards(self, app_label, *args, **kwargs):
        super(AddRemoteField, self).state_forwards(self.remote_app, *args, **kwargs)

    def database_forwards(self, app_label, *args, **kwargs):
        super(AddRemoteField, self).database_forwards(
            self.remote_app, *args, **kwargs)

    def database_backwards(self, app_label, *args, **kwargs):
        super(AddRemoteField, self).database_backwards(
            self.remote_app, *args, **kwargs)

然后我制作一个迁移文件:

from __future__ import unicode_literals

from django.db import models, migrations
from my_app.tools import AddRemoteField
from my_app.models import Client


class Migration(migrations.Migration):

    dependencies = [
        ('anikit', '0002_manual_user_migration'),
    ]

    operations = [
        AddRemoteField(
            remote_app='auth',
            model_name='user',
            name='client',
            field=models.ForeignKey(Client, verbose_name='client',
                                    null=True, blank=True),
            preserve_default=True,
        ),
    ]

【讨论】:

以上是关于如何从插件中修补南方处理的模型?的主要内容,如果未能解决你的问题,请参考以下文章

如何将南方全站仪数据导入南方cass

cad,南方cass,arcgis,mapgis 协同处理标准图幅矢量数据流程,最好是word的。

南方cassAL和AR怎么不能用,在那设置

南方CASS快捷键

海尔南方电网:这个AI引擎,装它!

远程医疗体系助力区域,南方电讯是认真的