2017-08-15 74 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一起实施。我编辑的问题更加明确 –