Saya mencoba melakukan beberapa metaclass hocus-pocus. Saya ingin Metaclass saya sendiri untuk mewarisi dari ModelBase dan kemudian saya ingin menambahkan logika tambahan dengan memperluas metode __new__-nya. Namun saya pikir ada sesuatu aneh terjadi dengan urutan MRO/warisan dalam cara saya menggunakannya.

Inilah situasi dasarnya:

from django.db.models import Model, ModelBase


class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        # As I am trying to extend `ModelBase`, I was expecting this
        # call to `super` to give me the return value from here:

        # https://github.com/django/django/blob/master/django/db/models/base.py#L300

        # And that I would be able to access everyhing in `_meta` with
        # `clsobj._meta`. But actually this object is
        # `MyAbstractModel` and has no `_meta` property so I'm pretty
        # sure `__new__` isn't being called on `ModelBase` at all at
        # this point.
        clsobj = super().__new__(cls, name, bases, attrs)

        # Now, I want to have access to the `_meta` property setup by
        # `ModelBase` so I can dispatch on the data in there. For
        # example, let's do something with the field definitions.
        for field in clsobj._meta.get_fields():
            do_stuff_with_fields()

        return clsobj


class MyAbstractModel(metaclass=CustomMetaclass):
    """This model is abstract because I only want the custom metaclass
    logic to apply to those models of my choosing and I don't want to
    be able to instantiate it directly. See the class definitions below.
    """
    class Meta:
        abstract = True

class MyModel(Model):
    """Regular model, will be derived from metaclass `ModelBase` as usual.
    """
    pass

class MyCustomisedModel(MyAbstractModel):
    """This model should enjoy the logic defined by our extended `__new__` method.
    """
    pass

Ada ide mengapa __new__ di ModelBase tidak dipanggil oleh CustomMetaClass? Bagaimana saya bisa memperpanjang ModelBase dengan benar dengan cara ini? Saya cukup yakin pewarisan metaclass mungkin terjadi tapi sepertinya aku kehilangan sesuatu...

0
jhrr 29 Desember 2017, 19:29

1 menjawab

Jawaban Terbaik

Cara mendapatkan clsobj dengan atribut _meta sesederhana:

class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        bases = (Model,)
        clsobj = super().__new__(cls, name, bases, attrs)

        for field in clsobj._meta.get_fields():
            do_stuff_with_fields()

        return clsobj

Dan kita dapat melakukan hal yang sama dengan MyAbstractModel(Model, metaclass=CustomMetaclass).

Namun, keberhasilan akhir di sini masih bergantung pada jenis pekerjaan yang ingin kita lakukan dalam metode __new__. Jika kita ingin mengintrospeksi dan bekerja dengan bidang kelas menggunakan metaprogramming, kita perlu menyadari bahwa kita mencoba untuk menulis ulang kelas menggunakan __new__ pada waktu import dan dengan demikian (karena ini adalah Django) registri aplikasi belum siap dan ini dapat menyebabkan pengecualian dimunculkan jika kondisi tertentu muncul (misalnya kami dilarang mengakses atau bekerja dengan hubungan terbalik). Ini terjadi di sini bahkan ketika Model diteruskan ke __new__ sebagai basis.

Kita dapat menghindari setengah dari beberapa masalah tersebut dengan menggunakan panggilan non-publik berikut ke _get_fields (yang Django lakukan sendiri di tempat-tempat tertentu):

class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        bases = (Model,)
        clsobj = super().__new__(cls, name, bases, attrs)

        for field in clsobj._meta._get_fields(reverse=False):
            do_stuff_with_fields()

        return clsobj

Tetapi tergantung pada skenario dan apa yang ingin kita capai, kita mungkin masih menemui masalah; misalnya, kami tidak akan dapat mengakses hubungan terbalik apa pun menggunakan metaclass kami. Jadi masih kurang bagus.

Untuk mengatasi pembatasan ini, kita harus memanfaatkan sinyal di registri aplikasi untuk membuat kelas kita sedinamis yang kita inginkan dengan akses penuh ke _meta.get_fields.

Lihat tiket ini: https://code.djangoproject.com/ticket/24231

Takeaway utama adalah: "kelas model Django bukanlah sesuatu yang Anda izinkan untuk bekerja di luar konteks registri aplikasi yang disiapkan."

0
jhrr 22 Maret 2018, 15:23