Di Django, saya mencoba membuat model dasar yang dapat digunakan untuk melacak versi berbeda dari beberapa data model lain secara transparan. Sesuatu seperti:

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)

Masalah yang saya miliki adalah SomeData.name sebenarnya tidak unik, tupel ('version_number', 'name' ) adalah.

Saya tahu saya bisa menggunakan kelas Meta di SomeData dengan unique_together tapi saya bertanya-tanya apakah ini bisa dilakukan dengan cara yang lebih transparan. Yaitu, secara dinamis memodifikasi/membuat bidang unique_together ini.

Catatan terakhir: mungkin menangani ini dengan pewarisan model bukanlah pendekatan yang benar tetapi tampaknya cukup menarik bagi saya jika saya dapat menangani masalah keunikan bidang ini.

EDIT: Jelas kata 'dinamis' dalam konteks ini menyesatkan. Saya ingin keunikan dipertahankan di tingkat basis data. Idenya adalah untuk memiliki versi data yang berbeda.

A berikan di sini contoh kecil dengan model di atas (dengan asumsi pewarisan model abstrak, sehingga semuanya ada di tabel yang sama):

Basis data dapat berisi:

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

Dan manajer model khusus saya akan memfilter data (mengambil nomor versi maksimum untuk setiap id unik) sehingga: SomeData.objects.all() akan kembali

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

Dan juga akan menawarkan metode lain yang dapat mengembalikan data seperti pada versi n-1.

Jelas saya akan menggunakan stempel waktu alih-alih nomor versi sehingga saya dapat mengambil data pada tanggal tertentu tetapi prinsipnya tetap sama.

Sekarang masalahnya adalah ketika saya melakukannya ./manage.py makemigrations dengan model di atas, itu akan menegakkan keunikan pada SomeData.name dan pada SomeData.id ketika yang saya butuhkan adalah keunikan pada ('SomeData.id',' version_number') dan ('SomeData.name', 'version_number'). Secara eksplisit, saya perlu menambahkan beberapa bidang dari model dasar ke bidang model yang diwarisi yang dinyatakan sebagai unik di db dan ini setiap kali perintah manage.py dijalankan dan/atau server produksi berjalan .

2
Bob Morane 15 Agustus 2017, 08:49

2 jawaban

Jawaban Terbaik

Oke, saya mengakhiri subkelas ModelBase dari Django.db.models.base sehingga saya dapat memasukkan ke dalam 'unique_together' bidang apa pun yang dinyatakan sebagai unik. Ini kode saya saat ini. Itu belum mengimplementasikan manajer dan menyimpan metode tetapi batasan keunikan db ditangani dengan benar.

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()
0
Bob Morane 19 Agustus 2017, 16:08

Dengan "memodifikasi/membuat bidang unique_together ini secara dinamis", saya berasumsi maksud Anda Anda ingin menerapkan ini di tingkat kode, bukan di tingkat basis data.

Anda dapat melakukannya dalam metode save() model Anda:

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)

Semoga membantu!

2
Jahongir Rahmonov 15 Agustus 2017, 06:41