Django-filter: TypeError pada Django startup

Dibuat pada 27 Jul 2018  ·  12Komentar  ·  Sumber: carltongibson/django-filter

Pertama-tama, terima kasih telah mempertahankan paket yang luar biasa ini 👍
Saya baru saja memperbarui Django-filter 1.1.0 ke 2.0.0 dan sekarang CBV berikut gagal saat startup Django:

class ForventetRegnskab(ABC, LoginRequiredMixin, UserPassesTestMixin, FilterView):
    """
    Abstract base class for all "brugervisninger".
    Each subclass must set the appropriate filterset_class and queryset.
    """
    login_url = 'home'
    raise_exception = True
    context_object_name = 'context'
    template_name = 'forventet_regnskab/content.html'

    <strong i="7">@property</strong>
    <strong i="8">@abstractmethod</strong>
    def filterset_class(self) -> Optional[FilterView.filterset_class]:
        pass

    <strong i="9">@property</strong>
    <strong i="10">@abstractmethod</strong>
    def queryset(self):
        pass

    <strong i="11">@has_sted1_permission</strong>
    def user_passes_test(self, request, kwargs) -> bool:
        """
        Deny a request with a permission error if the method returns False.

        :param request: The request object
        :param kwargs: self.kwargs
        """
        pass

    def test_func(self):
        return self.user_passes_test(self.request, self.kwargs)

    def get_context_data(self, **kwargs):
        context = super(ForventetRegnskab, self).get_context_data(**kwargs)
        filter_object = self.filterset_class(self.request.GET, queryset=self.get_queryset())

        # Add context variables
        context['title'] = constants.FORVENTET_REGNSKAB_TITLE
        context['filter_sum'] = self.filter_sum(filter_queryset=filter_object.qs)

        return context

    def get_queryset(self):
        aar = self.request.GET.get('aar')
        maaned = self.request.GET.get('maaned')
        kasse = self.request.GET.get('kasse')

        if aar and maaned and kasse:
            query = self.queryset.filter(sted1=self.kwargs['sted1']) \
                .annotate(
                forbrugsprocent=Case(
                    When(Q(korrigeret_budget__gt=0),
                         then=(F('akkumuleret_regnskab') / F('korrigeret_budget')) * 100),
                    default=0,
                    output_field=IntegerField()),
                mer_mindre_forbrug=Func(
                    F('forventet_regnskab'), function='ABS') - Func(F('korrigeret_budget'), function='ABS'))
        else:
            query = self.queryset.none()

        return query

    <strong i="12">@staticmethod</strong>
    def filter_sum(filter_queryset: QuerySet) -> QuerySet:
        """
        Aggregate the filtered queryset.
        """
        filter_sum = filter_queryset.aggregate(
            korrigeret_budget_sum=Sum('korrigeret_budget'),
            regnskab_sum=Sum('regnskab'),
            akkumuleret_regnskab_sum=Sum('akkumuleret_regnskab'),
            forventet_regnskab_sum=Sum('forventet_regnskab'),
            forbrugsprocent_sum=Case(
                When(Q(korrigeret_budget_sum__gt=0),
                     then=(Sum('akkumuleret_regnskab') / Sum('korrigeret_budget')) * 100),
                default=0,
                output_field=IntegerField()),
            mer_mindre_forbrug_sum=Func(
                Sum('forventet_regnskab'), function='ABS') - Func(Sum('korrigeret_budget'), function='ABS'))

        return filter_sum

Setiap subclass mengimplementasikan filterset_class dan querysetnya sendiri. Berikut ini contohnya:

class SpecialBrugervisning(ForventetRegnskab):
    filterset_class = filters.SpecialBrugervisning
    queryset = models.SpecialBrugervisning.objects.all()

Saya mendapatkan kesalahan ini saat startup:

Berkas "[...]\oekonomistyring\forventet_regnskab\views.py", baris 18, di
kelas ForventetRegnskab(ABC, LoginRequiredMixin, UserPassesTestMixin, FilterView):
TypeError: konflik metaclass: metaclass dari kelas turunan harus merupakan subclass (tidak ketat) dari metaclass dari semua basisnya

Hmmm... !

Apakah perilaku ini diinginkan atau ini bug?

Komentar yang paling membantu

Ini tidak terlihat spesifik untuk Django-filter, lebih dari itu sebuah kelas tidak dapat mewarisi dua metaclass yang terpisah.

class AMeta(type): pass
class BMeta(type): pass

class A(metaclass=AMeta): pass
class B(metaclass=BMeta): pass

# fails with metaclass error
class C(A, B): pass

# passes
class CMeta(AMeta, BMeta): pass
class C(A, B, metaclass=CMeta): pass

Perubahan yang relevan dalam 2.x adalah bahwa FilterMixin menggunakan FilterMixinRenames metaclass, menciptakan konflik dengan ABCMeta . Anda harus dapat membuat metclass tanpa secara langsung merujuk ke metaclass tampilan.

```python
kelas MyMeta(ABCMeta, ketik(FilterView)):
lulus

kelas ForventetRegnskab(LoginRequiredMixin, UserPassesTestMixin, FilterView, metaclass=MyMeta):
lulus
``

Semua 12 komentar

Apa yang terjadi jika Anda menjatuhkan warisan ABC ?

(Lebih lanjut: adakah kemungkinan Anda dapat mengurangi ini menjadi contoh minimal sehingga saya dapat bermain sendiri?)

Hai! Ia bekerja tanpa warisan ABC 👍

Contoh minimal:

class ForventetRegnskabNoABC(LoginRequiredMixin, UserPassesTestMixin, FilterView):
    login_url = 'home'
    raise_exception = True
    context_object_name = 'context'
    template_name = 'content.html'

    filterset_class = filters.my_filterset
    queryset = models.my_queryset

    def test_func(self):
        return True

    def get_context_data(self, **kwargs):
        context = super(ForventetRegnskabNoABC, self).get_context_data(**kwargs)
        filter_object = self.filterset_class(self.request.GET, queryset=self.get_queryset())

        # Add context variables
        context['filter_sum'] = self.filter_sum(filter_queryset=filter_object.qs)

        return context

    def get_queryset(self):
        return self.queryset

    <strong i="6">@staticmethod</strong>
    def filter_sum(filter_queryset: QuerySet) -> QuerySet:
        filter_sum = filter_queryset.aggregate()

        return filter_sum

Contoh minimal untuk membuat ulang TypeError adalah:

class Foo(abc.ABC, FilterView):
    pass

Ini tidak terlihat spesifik untuk Django-filter, lebih dari itu sebuah kelas tidak dapat mewarisi dua metaclass yang terpisah.

class AMeta(type): pass
class BMeta(type): pass

class A(metaclass=AMeta): pass
class B(metaclass=BMeta): pass

# fails with metaclass error
class C(A, B): pass

# passes
class CMeta(AMeta, BMeta): pass
class C(A, B, metaclass=CMeta): pass

Perubahan yang relevan dalam 2.x adalah bahwa FilterMixin menggunakan FilterMixinRenames metaclass, menciptakan konflik dengan ABCMeta . Anda harus dapat membuat metclass tanpa secara langsung merujuk ke metaclass tampilan.

```python
kelas MyMeta(ABCMeta, ketik(FilterView)):
lulus

kelas ForventetRegnskab(LoginRequiredMixin, UserPassesTestMixin, FilterView, metaclass=MyMeta):
lulus
``

Terima kasih atas tulisannya @rpkilby!

Hai kalian berdua!

@rpkilby jawaban yang sangat jelas - dan solusi Anda berfungsi sebagai catatan! 🥇

Semua yang terbaik, Henrik

Halo lagi!

Saya harap Anda punya waktu untuk mengklarifikasi satu hal: Mengapa Anda menggunakan type(FilterView) dan bukan hanya FilterView?
Apakah akan ditafsirkan sebagai "metaclass MyABC IS metaclass FilterView"?

Jenis kelas adalah metaclass-nya. yaitu, metaclass default adalah type .

type(FilterView) membuat metaclass FilterView

Tidak persis, itu hanya mendapatkan metaclass (tidak membuat metaclass baru ). Ini adalah alternatif untuk mereferensikan FilterMixinRenames secara eksplisit.

Kata kunci digunakan untuk menetapkan 1) sebagai satu-satunya metaclass untuk semua subclass?

Itu akan berlaku untuk kelas dan subkelasnya, kecuali diganti oleh metaclass lain.

Terima kasih sekali lagi, itu membantu.

Segala sesuatu di Python adalah objek, dan semuanya dibangun dari kelas. yaitu, kelas dari sebuah instance adalah kelasnya, kelas dari sebuah kelas adalah metaclass-nya. Satu-satunya kasus khusus adalah basis type , yang memiliki tipe type .

Jika Anda mengatakan bahwa suatu kelas tidak dapat mewarisi dua kelas meta yang terpisah, masuk akal untuk "menggabungkan" mereka menjadi satu dan menggunakannya dalam subkelas?

saya salah ketik. Kelas tidak dapat dibuat dari dua kelas meta. Namun, itu dapat dibuat dari metaclass yang mewarisi beberapa metaclass lainnya. Ini mirip dengan bagaimana Anda tidak dapat membuat instance objek dengan dua kelas. Anda perlu membuat satu kelas yang mewarisi keduanya dan membuat instance objek dari itu. Pembuatan kelas adalah dengan cara yang sama.

Saya berasumsi bahwa metaclass FilterClass diwarisi dari FilterMixinRenames...

Metaclassnya adalah FilterMixinRenames . Anda dapat memverifikasi ini dengan type(FilterView) .

Terima kasih! Semuanya masuk akal sekarang - ya, saya melakukan pengecekan tipe yang ditambahkan - ini bukan sihir :santai:

Apakah halaman ini membantu?
0 / 5 - 0 peringkat