2017-08-15 14 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')です。

SomeDataMetaクラスを使用できることはわかっていますが、これはもっと透明な方法で行うことができるかどうか疑問に思っていました。つまり、この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... 

と私のカスタムを

Aは、ここでは上記のモデル(すべてが同じテーブルにあるように、抽象モデルの継承を前提とする)との小さな例を与えますようにモデルマネージャは、(すべての一意のIDのバージョン番号に最大を取って)データをフィルタします: SomeData.objects.all() は

 
id | name 
1 | bobby 
2 | nicky 
3 | momo 
を返します

と同様に、バージョンn-1のようにデータを返すことのできるメソッドを提供します。

明らかに、バージョン番号の代わりにタイムスタンプを使用して、特定の日付にデータを取得できるようにしますが、原則は同じです。

今の問題は、私は ./manage.py makemigrations上記のモデルと を行うとき、私は必要なのに一意であるとき、それはSomeData.name上とSomeData.idに一意性を強制するということです( 'SomeData.id'、 'version_number')( 'SomeData.name'、 'version_number')。明示的に、ベースモデルのいくつかのフィールドを、db内でユニークであると宣言された継承モデルのフィールドに追加する必要があります。manage.pyコマンドが実行され、実稼働サーバーが実行されます。

+0

私はそれを何度か読んでも問題は発生する可能性がありますが、コード例を追加できますか? –

+0

モデルの各フィールドにこのunique_togetherフィルタが必要ですか?これはdbレベルで強制されることに注意してください。 –

+0

私は質問をより明確に編集しました。ありがとう –

答えて

0

私は 'unique_together'に一意であると宣言されたフィールドを挿入できるように、ModelBaseをdjango.db.models.baseからサブクラス化しました。ここに私の現在のコードです。まだマネージャーと保存メソッドは実装されていませんが、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() 
1

"動的に変更する/このフィールドを作成する"とは、データベースレベルではなく、コードレベルでこれを実行することを意味します。

あなたは、モデルの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と組み合わせて一意性を強制します。質問をより明示的に編集しました –

関連する問題