改变继承的Django模型

时间:2017-08-15 05:49:19

标签: python django

在Django中,我尝试创建一个基本模型,可以用来以透明的方式跟踪其他模型数据的不同版本。类似的东西:

class Warehouse(models.Model):

    version_number = models.IntegerField()
    objects = CustomModelManagerFilteringOnVersionNumber()

    class Meta:
        abstract=True

    def save(self, *args, **kwargs):
        # handling here version number incrementation
        # never overwrite data but create separate records,
        # don't delete but mark as deleted, etc.
        pass

class SomeData(Warehouse):
    id = models.IntegerField( primary_key=True )
    name = models.CharField(unique=True)

我遇到的问题是SomeData.name实际上不是唯一的,元组('version_number', 'name' )是。

我知道我可以使用Meta中的SomeDataunique_together,但我想知道这是否可以以更透明的方式完成。也就是说,动态修改/创建此unique_together字段。

最后注意事项:使用模型继承来处理这个问题并不是正确的方法,但如果我能处理这个字段唯一性问题,那对我来说看起来很有吸引力。

编辑:显然,“动态”这个词是'在这种情况下是误导。我确实希望在数据库级别保留唯一性。我们的想法是拥有不同版本的数据。

这里给出一个带有上述模型的小例子(假设抽象模型继承,所以一切都在同一个表中):

数据库可以包含:

version_number | id | name  | #comment
0              | 1  | bob   | first insert
0              | 2  | nicky | first insert
1              | 1  | bobby | bob changed is name on day x.y.z
0              | 3  | malcom| first insert
1              | 3  | malco | name change
2              | 3  | momo  | again...

我的自定义模型管理器会过滤数据(为每个唯一ID获取最大版本号),以便:     SomeData.objects.all() 会回来

id | name
1  | bobby
2  | nicky
3  | momo

并且还会提供其他可以返回数据的方法,如版​​本n-1。

显然我会使用时间戳而不是版本号,以便我可以在给定日期检索数据但原则保持不变。

现在的问题是,当我这样做的时候     ./manage.py makemigrations 使用上面的模型,它将强制 SomeData.name SomeData.id 上的唯一性,当我需要的是上的唯一性时(' SomeData.id& #39;,' version_number')(' SomeData.name',' version_number')。显然,我需要将基本模型中的一些字段附加到在数据库中声明为唯一的继承模型的字段中,并且任何时候运行 manage.py 命令和/或生成服务器运行

2 个答案:

答案 0 :(得分:2)

通过“动态修改/创建此unique_together字段”,我假设您的意思是要在代码级别而不是在数据库级别强制执行此操作。

您可以使用模型的save()方法执行此操作:

from django.core.exceptions import ValidationError

def save(self, *args, **kwargs):
    if SomeData.objects.filter(name=self.name, version_number=self.version_number).exclude(pk=self.pk).exists():
        raise ValidationError('Such thing exists')

    return super(SomeData, self).save(*args, **kwargs)

希望它有所帮助!

答案 1 :(得分:0)

好的,我结束了对django.db.models.base中的ModelBase的子类化,以便我可以插入' unique_together'声明为唯一的任何字段。这是我目前的代码。它尚未实现管理器和保存方法,但正确处理了db唯一性约束。

from django.db.models.options import normalize_together
from django.db.models.base import ModelBase
from django.db.models.fields import Field

class WarehouseManager( models.Manager ):
    def get_queryset( self ):
        """Default queryset is filtered to reflect the current status of the db."""

        qs = super( WarehouseManager, self ).\
             get_queryset().\
             filter( wh_version = 0 )

class WarehouseModel( models.Model ):
    class Meta:
        abstract = True

    class __metaclass__(ModelBase):
        def __new__(cls, name, bases, attrs):
            super_new = ModelBase.__new__

            meta = attrs.get( 'Meta', None )
            try:
                if attrs['Meta'].abstract == True:
                    return super_new(cls, name, bases, attrs )
            except:
                pass

            if meta is not None:
                ut = getattr( meta, 'unique_together', () )
                ut = normalize_together( ut )
                attrs['Meta'].unique_together = tuple( k+('wh_version',) for k in ut )

            unique_togethers = ()
            for fname,f in attrs.items():
                if fname.startswith( 'wh_' ) or not isinstance( f, Field ):
                    continue

                if f.primary_key:
                    if not isinstance( f, models.AutoField ):
                        raise AttributeError( "Warehouse inherited models cannot "
                                "define a primary_key=True field which is not an "
                                "django.db.models.AutoField. Use unique=True instead." )
                    continue

                if f.unique:
                    f._unique = False
                    unique_togethers += ( (fname,'wh_version'), )

            if unique_togethers:
                if 'Meta' in attrs:
                    attrs['Meta'].unique_together += unique_togethers
                else:
                    class DummyMeta: pass
                    attrs['Meta'] = DummyMeta
                    attrs['Meta'].unique_together = unique_togethers

            return super_new(cls, name, bases, attrs )

    wh_date = models.DateTimeField(
        editable=False,
        auto_now=True,
        db_index=True
    )
    wh_version = models.IntegerField(
        editable=False,
        default=0,
        db_index=True,
    )

    def save( self, *args, **kwargs ):
        # handles here special save behavior...
        return super( WarehouseModel, self ).save( *args, **kwargs )

    objects = WarehouseManager()

class SomeData( WarehouseModel ):
    pid = models.IntegerField( unique=True )

class SomeOtherData( WarehouseModel ):
    class Meta:
        unique_together = ('pid','chars')
    pid = models.IntegerField()
    chars = models.CharField()