简体   繁体   English

改变继承的 Django 模型的唯一字段

[英]Altering unique fields of inherited Django model

In Django, I'm trying to create a base model that can be used to track the different version of some other model's data in a transparent way.在 Django 中,我正在尝试创建一个基本模型,该模型可用于以透明的方式跟踪其他模型数据的不同版本。 Something like:就像是:

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)

The problem I have is that SomeData.name is actually not unique, the tuple ('version_number', 'name' ) is.我的问题是SomeData.name实际上不是唯一的,元组('version_number', 'name' )是。

I know I can use the Meta class in SomeData with unique_together but I was wondering whether this could be done in a more transparent way.我知道我可以将SomeDataMeta类与SomeData一起unique_together但我想知道这是否可以以更透明的方式完成。 That is, dynamically modifying/creating this unique_together field.也就是说,动态修改/创建这个unique_together字段。

Final note: maybe handling this with model inheritance is not the correct approach but it looked pretty appealing to me if I can handle this field uniqueness problem.最后一点:也许用模型继承来处理这个问题不是正确的方法,但如果我能处理这个字段唯一性问题,它看起来对我很有吸引力。

EDIT: Obviously the word 'dynamic' in this context is misleading.编辑:显然,在这种情况下,“动态”一词具有误导性。 I do want the uniqueness to be preserved at the database level.我确实希望在数据库级别保留唯一性。 The idea is to have different version of the data.这个想法是拥有不同版本的数据。

A give here a small example with the above model (assuming an abstract model inheritance, so that everything is in the same table):在这里给出一个上面模型的小例子(假设一个抽象模型继承,所以一切都在同一个表中):

The database could contain:数据库可能包含:

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...

and my custom model manager would filter the data (taking the max on version number for every unique id) so that: SomeData.objects.all() would return并且我的自定义模型管理器将过滤数据(获取每个唯一 ID 的最大版本号),以便: SomeData.objects.all() 将返回

id | name
1  | bobby
2  | nicky
3  | momo

and would also offer others method that can return data as in version n-1.并且还将提供其他方法可以返回版本 n-1 中的数据。

Obviously I will use a timestamp instead of a version number so that I can retrieve the data at a given date but the principle remains the same.显然,我将使用时间戳而不是版本号,以便我可以在给定日期检索数据,但原理保持不变。

Now the problem is that when I do ./manage.py makemigrations with the models above, it will enforce uniqueness on SomeData.name and on SomeData.id when what I need is uniqueness on ('SomeData.id','version_number') and ('SomeData.name', 'version_number') .现在的问题是,当我对上面的模型执行./manage.py makemigrations时,当我需要的是('SomeData.id','version_number') 上的唯一性时,它会在SomeData.nameSomeData.id上强制执行唯一性和('SomeData.name', 'version_number') Explicitly, I need to append some fields from the base models into the fields of the inherited models declared as unique in the db and this any times the manage.py command is run and/or the production server runs.明确地,我需要将基本模型中的一些字段附加到在 db 中声明为唯一的继承模型的字段中,并且在运行manage.py命令和/或生产服务器运行时都可以这样做。

By "dynamically modifying/creating this unique_together field", I assume you mean you want to enforce this at the code level, not at the database level.通过“动态修改/创建这个unique_together字段”,我假设您的意思是您想在代码级别而不是数据库级别强制执行此操作。

You can do it in the save() method of your model:您可以在模型的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)

Hope it helps!希望能帮助到你!

Ok, I ended subclassing the ModelBase from django.db.models.base so that I can insert into 'unique_together' any fields declared as unique.好的,我结束了从 django.db.models.base 对 ModelBase 进行子类化,以便我可以将声明为唯一的任何字段插入到“unique_together”中。 Here is my current code.这是我当前的代码。 It does not yet implements the manager and save methods but the db uniqueness constraints are handled correctly.它尚未实现管理器和保存方法,但正确处理了数据库唯一性约束。

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()

Just to add to @Bob's answer you may need to use the __metaclass__ class as following只是为了添加到__metaclass__的答案中,您可能需要使用__metaclass__类,如下所示

class ModelBaseMeta(BaseModel):
    
    def __new__(cls, name, bases, attrs):
        ...
    ...

class YourAbstractClass(models.Model, metaclass=ModelBaseMeta):
    
    class Meta:
        abstract = True

Instead of putting the __metaclass__ definition directly inside the abstract class.而不是将__metaclass__定义直接放在抽象类中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM