Home > Enterprise >  Django FormView and ListView multiple inheritance error
Django FormView and ListView multiple inheritance error

Time:11-26

Problem

I mad a AccesCheck Mixin, and view named ListFormView that inherits AccessCheck, FormView and ListView to show list and create/update Worker objects. But when I try to add new data by POST method, django keeps returning Attribute Error : Worker object has no attribute 'object_list' error.

What is more confusing to me is that whole ListFormView is duplication of another class based view that is used in another app, and the original one is running without any problems. I've doublechecked all my codes and still have no clue to fix this problem.

[AccessCheck]

class AccessCheck(LoginRequiredMixin, UserPassesTestMixin, View):
    def test_func(self, *args, **kwargs):
        access = [x.id for x in Auth.objects.filter(auth_id = self.kwargs['target'])]
        return self.request.user.is_superuser or selr.request.user.id in access

    def handle_no_permission(self):
        return redirect('index')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['shop'] = Shop.objects.get(shop_id = self.kwars['shop_id'])
        return context

[ListFormView]

class ListFormView(AccessCheck, FormView, ListView):
    template_name = 'my_template_name.html'
    context_object_name = 'workers'
    form_class = WorkerForm
    success_url = './my_url'

    def form_valid(self, form):
        data = form.save()
        return super().form_valid(form)

    def get_queryset(self, *args, **kwargs):
        return Worker.objects.filter(shop_id = self.kwargs['shop_id'])

How it was solved

[ListFormView]

class ListFormView(AccessCheck, FormView, ListView):
    template_name = 'my_template_name.html'
    context_object_name = 'workers'
    form_class = WorkerForm
    success_url = './my_url'

    def get_context_data(self, **kwargs):
        self.object_list = Worker.objects.filter(shop_id = self.kwargs['shop_id'])
        context = super().get_context_data(**kwargs)
        return context

    def form_valid(self, form):
        data = form.save()
        return super().form_valid(form)

    def get_queryset(self, *args, **kwargs):
        return Worker.objects.filter(shop_id = self.kwargs['shop_id'])

CodePudding user response:

Multiple inheritance is a rich source of bugs if you don't make everything except the last superclass a Mixin class (derived from object). I'd have suggested

class AccessCheckMixin(LoginRequiredMixin, UserPassesTestMixin):
    # merging two mixins and adding methods is fine
    def test_func(self, *args, **kwargs):
        access = [x.id for x in Auth.objects.filter(auth_id = self.kwargs['target'])]
        return self.request.user.is_superuser or selr.request.user.id in access

    def handle_no_permission(self):
        return redirect('index')

    # get_context_data belongs in a View subclass

class ListFormView(AccessCheckMixin, FormView, ListView):
    # I have misgivings about merging FormView and ListView, but maybe 
    ...
    def get_context_data(self, **kwargs):
        # it belongs here, but super() is going to invoke only one of the
        # get_context_data implemenations in one of its parents. 

Bookmark Classy Class-based views for browsing what's in the Django CBVs.

CodePudding user response:

That's classic problem of OOP. You have two inherited classes with the same method get_context_data(). The target class always getting code of the method from first inherited class. In this case ListFormView.get_context_data() has the same code as the AccessCheck.get_context_data() and ListFormView class doesn't konw anithing about code in AccessCheck.get_context_data() method (where self.object_list = ... is defined).

Here is example:

class First:
    def f(self):
        print('Code from First')

class Second:
    def f(self):
        print('Code from Second')

class A(First, Second):
    pass


A().f() >>> "Code from First"

The possible solutions is to move correct class to the top of inheritance list:

#         ╔═══════╗  swithch them
class A(Second, First):
    pass

A().f() >>> "Code from Second"

Or you can define your own f method in class A and use super with argument to define where you wanted to start method resoulution. To define what to write as first argument of super run A.__mro__ and chose class before target in this list:

class A(First, Second):    
    def f(self):
        super(First, self).f()

A.__mro__ >>> (<class '__main__.A'>, <class '__main__.First'>, <class '__main__.Second'>, <class 'object'>)
A().f() >>> "Code from Second"

I recommended you to read about OOP in python and multiple inheritance.

* your possible solution is

# make from this line
class ListFormView(AccessCheck, FormView, ListView)
# this one
class ListFormView(ListView, AccessCheck, FormView)
  • Related