Django - Mixing ListView and CreateView
Asked Answered
S

5

18

I'm want to create one page with a form, and every time I submit the form it adds an item to the list below the form.

I can make it work using 2 pages:

  • one page using the mixin CreateView to add items
  • one page ListView to have the list.

But I'm trying to have the form and the list on the same page. So I tried to create a class with both mixin:

class FormAndListView(ListView, CreateView):
    pass

Then I've used this class:

FormAndListView.as_view(
    queryset=PdfFile.objects.order_by('id'),
    context_object_name='all_PDF',
    success_url = 'listview',
    form_class = UploadFileForm,
    template_name='textfrompdf/index.html',)),

But when I try to load the page, I get the error: Exception Value: 'FormAndListView' object has no attribute 'object'

Traceback:
File "C:\Program Files\Python_2.7\lib\site-packages\django\core\handlers\base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\base.py" in view
  47.             return self.dispatch(request, *args, **kwargs)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\base.py" in dispatch
  68.         return handler(request, *args, **kwargs)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\list.py" in get
  122.         return self.render_to_response(context)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\base.py" in render_to_response
  94.             template = self.get_template_names(),
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\list.py" in get_template_names
  134.             names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\detail.py" in get_template_names
  122.         if self.object and self.template_name_field:

Exception Type: AttributeError at /PDF/
Exception Value: 'FormAndListView' object has no attribute 'object'

I've no idea how to debug that. Where to start?

Stob answered 18/2, 2012 at 3:12 Comment(0)
S
8

I found the answer, there is 2 problems:

  • ListView and CreateView are "high level" mixin which aggregate "lower level" mixins. But these lower level mixins are not compatible together.
  • The View class calls directly the render_to_response(), but in my scenario, there is 2 view class and render_to_response() should only be called once at the end.

I was able "solve" this issue using the following steps:

Instead of calling ListView and CreateView, I used lower level mixins. Moreover I called explicitly BaseCreateView and BaseListView from which I "extracted" the form and object_list

class FormAndListView(BaseCreateView, BaseListView, TemplateResponseMixin):
    def get(self, request, *args, **kwargs):
        formView = BaseCreateView.get(self, request, *args, **kwargs)
        listView = BaseListView.get(self, request, *args, **kwargs)
        formData = formView.context_data['form']
        listData = listView.context_data['object_list']
        return render_to_response('textfrompdf/index.html', {'form' : formData, 'all_PDF' : listData},
                           context_instance=RequestContext(request))

It's not clean but it works!

Stob answered 19/2, 2012 at 1:39 Comment(1)
There's too much going on with this view and it will be difficult to mantain. The user jondykeman has a saner and more elegant solution to this problem.Voletta
T
40

I use a lot of views that involve a form and a list of objects. Rather than trying to mixin things I just add the queryset into the context data as below.

class UploadFileView(CreateView):
    form_class = UploadFileForm
    success_url = 'listview'
    template_name = 'textfrompdf/index.html'

    def get_context_data(self, **kwargs):
        kwargs['object_list'] = PdfFile.objects.order_by('id')
        return super(UploadFileView, self).get_context_data(**kwargs)
Tuition answered 14/10, 2012 at 15:32 Comment(3)
This is probably very elegant, but it would help to have some more explanation.Frear
Is there any problem with having bootstrap paginate the list with this usage?Itching
But this doesn't paginateBooking
G
11

Do not mix list and update views.
Instead, create two separate views for these tasks:

List view displays the list and a web form with action URL pointing to the create view.
Create view accepts POST data and

  • displays form with error message in case of failure;
  • redirects to the list view in case of success.

Also I've tried to use class-based views and found that they are too complex.
I think it is much easier to use old-style function views.

Glean answered 18/2, 2012 at 8:31 Comment(1)
Ho yes, good idea! I'm going to do that, but because i'm new to python and Django, I'd be interested to understand why it doesn't work or else how to find the source of my mistake. Thanks!Stob
S
8

I found the answer, there is 2 problems:

  • ListView and CreateView are "high level" mixin which aggregate "lower level" mixins. But these lower level mixins are not compatible together.
  • The View class calls directly the render_to_response(), but in my scenario, there is 2 view class and render_to_response() should only be called once at the end.

I was able "solve" this issue using the following steps:

Instead of calling ListView and CreateView, I used lower level mixins. Moreover I called explicitly BaseCreateView and BaseListView from which I "extracted" the form and object_list

class FormAndListView(BaseCreateView, BaseListView, TemplateResponseMixin):
    def get(self, request, *args, **kwargs):
        formView = BaseCreateView.get(self, request, *args, **kwargs)
        listView = BaseListView.get(self, request, *args, **kwargs)
        formData = formView.context_data['form']
        listData = listView.context_data['object_list']
        return render_to_response('textfrompdf/index.html', {'form' : formData, 'all_PDF' : listData},
                           context_instance=RequestContext(request))

It's not clean but it works!

Stob answered 19/2, 2012 at 1:39 Comment(1)
There's too much going on with this view and it will be difficult to mantain. The user jondykeman has a saner and more elegant solution to this problem.Voletta
C
4

I have made my own class to solve this problem. I don't know if it's better or worse, but it works too. I have tried to use the generic mixins and have tested that validation and pagination work.

The code in GitHub

class ListAppendView(MultipleObjectMixin,
    MultipleObjectTemplateResponseMixin,
    ModelFormMixin,
    ProcessFormView):
    """ A View that displays a list of objects and a form to create a new object.
    The View processes this form. """
    template_name_suffix = '_append'
    allow_empty = True

    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()
        if not allow_empty and len(self.object_list) == 0:
            raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
                          % {'class_name': self.__class__.__name__})
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        context = self.get_context_data(object_list=self.object_list, form=form)
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super(ListAppendView, self).post(request, *args, **kwargs)

    def form_invalid(self, form):
        self.object_list = self.get_queryset()
        return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))

If you try it and find any errors, please tell me here or in GitHub.

Copenhagen answered 18/6, 2012 at 17:51 Comment(0)
H
0

I got into this problem and solve it with the following code, the answer by @jondykeman does not have pagination and other utilities for base classes. the other approaches that are proposed are a little complicated than the following:

class ObjectCreateView(LoginRequiredMixin, MultipleObjectMixin, View):

   queryset = Wallet.objects.all()

   def get(self, request):
       self.object_list = super(ObjectCreateView, self).get_queryset().filter(user=request.user)
       allow_empty = super(ObjectCreateView, self).get_allow_empty()

       if not allow_empty:
           if super(ObjectCreateView, self).get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
               is_empty = not self.object_list.exists()
           else:
               is_empty = not self.object_list
           if is_empty:
               raise Http404()
       context = super(ObjectCreateView, self).get_context_data()
       form = CreateObjectForm()
       context['form'] = form
       return render(request, 'objects/object-list.html', context=context)

  def post(self, request):
       form = CreateWalletForm(request.POST)
       if form.is_valid():
        Object.objects.create(name=form.cleaned_data['name'], user=request.user)
           messages.success(request, 'Object is created')
       else:
           messages.error(request, utility.get_form_errors_as_string(form))
       return redirect('objects:create')
Honeymoon answered 28/12, 2020 at 7:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.