在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' )
是。
我知道我可以使用Meta
中的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.name 和 SomeData.id 上的唯一性,当我需要的是上的唯一性时(' SomeData.id& #39;,' version_number')和(' SomeData.name',' version_number')。显然,我需要将基本模型中的一些字段附加到在数据库中声明为唯一的继承模型的字段中,并且任何时候运行 manage.py 命令和/或生成服务器运行
答案 0 :(得分:2)
通过“动态修改/创建此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)
希望它有所帮助!
答案 1 :(得分:0)
好的,我结束了对django.db.models.base中的ModelBase的子类化,以便我可以插入' unique_together'声明为唯一的任何字段。这是我目前的代码。它尚未实现管理器和保存方法,但正确处理了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()