繁体   English   中英

改变继承的 Django 模型的唯一字段

[英]Altering unique fields of inherited Django model

在 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' )是。

我知道我可以将SomeDataMeta类与SomeData一起unique_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.id','version_number') 上的唯一性时,它会在SomeData.nameSomeData.id上强制执行唯一性和('SomeData.name', 'version_number') 明确地,我需要将基本模型中的一些字段附加到在 db 中声明为唯一的继承模型的字段中,并且在运行manage.py命令和/或生产服务器运行时都可以这样做。

通过“动态修改/创建这个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)

希望能帮助到你!

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

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

只是为了添加到__metaclass__的答案中,您可能需要使用__metaclass__类,如下所示

class ModelBaseMeta(BaseModel):
    
    def __new__(cls, name, bases, attrs):
        ...
    ...

class YourAbstractClass(models.Model, metaclass=ModelBaseMeta):
    
    class Meta:
        abstract = True

而不是将__metaclass__定义直接放在抽象类中。

暂无
暂无

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

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