簡體   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