2017-08-15 72 views
2

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

我知道我可以使用MetaSomeDataunique_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.nameSomeData.id當我需要的是在獨特性('SomeData.id','version_number')('SomeData.name','version_number')。明確地說,我需要將基本模型中的一些字段追加到db中聲明爲唯一的繼承模型的字段中,並且這個命令有時會運行並且/或者生產服務器運行。

+0

我讀了幾次,仍然可以得到的問題,你可以添加代碼示例? –

+0

您希望模型中的每個字段都具有此unique_together過濾器嗎?請記住,這是在db級別強制執行的。 –

+0

我編輯的問題更清楚。謝謝 –

回答

0

好的,我結束了從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() 
1

通過「動態修改/創建此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) 

希望它能幫助!

+0

也許我不清楚我的問題。我希望唯一性與db級別的version_number一起實施。我編輯的問題更加明確 –