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

时间:2013-05-23 09:31:52

标签: python django django-south monkeypatching django-migrations

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

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

由于主应用程序已经具有南方管理功能,因此拥有所有插件,因此我无法在这些模块的设置中更改迁移目录。

那么,我如何从其他南方应用程序中修补南方应用程序模型?

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

编辑:我在django迁移中添加了关于我现在的处理方式的答案。

3 个答案:

答案 0 :(得分:3)

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

如果所有模型都会自动跟踪,我稍后会看到下次迁移。

在我的新迁移文件中:

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')

答案 1 :(得分:1)

我发布了一个新的答案,因为我迁移到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,
        ),
    ]

答案 2 :(得分:0)

对于基督的爱,不要使用monkeypatching。使用继承,这就是它的用途。

只需让你的插件需要使用更多字段扩展现有模型,然后使用众多技术之一来获得模型的最专业的类实例。我使用下面的类作为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的技巧,以便在提供超类实例时获得相应的子类模型实例。