Django - Mixing ListView and CreateView - django

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?

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)

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.

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!

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.

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')

Related

Django: one url search in two models (cbv)

Using Django, I'm looking for a way to use one url patern (with slug) to query one model and if nothing is found query a second model. I'm using Class Based Views.
I am following this answer, and the next View is being called. But then I get the following error:
"Generic detail view must be called with either an object pk or a slug."
I can't figure out how to pass the slug to the next View.
My url:
url(r'^(?P<slug>[-\w]+)/$', SingleView.as_view(), name='singleview'),
My CBV's:
class SingleView(DetailView):
def dispatch(self, request, *args, **kwargs):
post_or_page_slug = kwargs.pop('slug')
if Page.objects.filter(slug=post_or_page_slug).count() != 0:
return PageDetailView.as_view()(request, *args, **kwargs)
elif Post.objects.filter(slug=post_or_page_slug).count() != 0:
return PostDetailView.as_view()(request, *args, **kwargs)
else:
raise Http404
class PageDetailView(DetailView):
model = Page
template_name = 'page-detail.html'
class PostDetailView(DetailView):
model = Post
template_name = 'post-detail.html'
The problem is that you are popping the slug, which removes it from kwargs. This means that the slug is not getting passed to the view.
You can change it to:
post_or_page_slug = kwargs.pop['slug']
I would usually discourage calling MyView.as_view(request, *args, **kwargs) inside another view. Class based views are intended to be extended by subclassing, not by calling them inside other views.
For the two views in your example, you could combine them into a single view by overriding get_object and get_template_names.
from django.http import Http404
class PageOrPostDetailView(DetailView):
def get_object(self):
for Model in [Page, Post]:
try:
object = Model.objects.get(slug=self.kwargs['slug'])
return object
except Model.DoesNotExist:
pass
raise Http404
def get_template_names(self):
if isinstance(self.object, Page):
return ['page-detail.html']
else:
return ['post-detail.html']

CreateView + redefinition of form_valid = didn't return an HttpResponse object

Django 1.10strong text
Could you help me understand why this results in:
The view wiki.views.WikiCreate didn't return an HttpResponse object. It returned None instead.
post: I didn't interfere with the chain of inheritance when redefining post method. I just intercepted the parameters from url.
form_valid: I returned the HttpResponse. But seemingly somewhere later it disappeared.
urlpatterns = [
url(r'^(?P<model>[-\w]+)/(?P<pk>\d+)/wiki/create/$', WikiCreate.as_view(), name='wiki_create'),
]
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def post(self, request, *args, **kwargs):
#For wiki history.
self.author = request.user
# Parameters from url reflect fields not shown to user.
self.related_model = kwargs.get('model')
self.related_object_id = kwargs.get('pk')
super(WikiCreate, self).post(request, *args, **kwargs)
def form_valid(self, form):
"""
Only "article" field was shown to tue user.
Now we save fields that were not explicitly shown (but were in the url).
"""
self.object = form.save(commit=False)
self.object.related_model = self.related_model
self.object.related_id = self.related_object_id
self.object.save()
return HttpResponseRedirect(self.get_success_url())
You didn't return the response from the super call in post.
Note it's rarely a good idea to override that method, and you certainly didn't need to here. You can access those kwargs directly in form_valid.

Django SessionWizardView get_template_names() issue

If I use the template_name variable, my SessionWizardView works like a pycharm!
I am having troubles with the get_template_names() method provided by the SessionWizardView.
My first template using the get_template_names() method renders perfectly displaying the correct url.
http://127.0.0.1:8000/wow/character_creation/
I submit the form, and my second form using the get_template_name() method renders perfectly displaying the correct url and form.
http://127.0.0.1:8000/wow/character_creation/
I submit my second form, or if I press the prev step or first step, the following url is displayed
http://127.0.0.1:8000/character_creation/
Here is the error message:
Page not found (404)
Request Method: POST
Request URL: http://127.0.0.1:8000/character_creation/
Anyone having a reason why the /wow/ part of my url has been removed when submitting the second form? Is there a bug in the get_template_names() method?
Here is my views.py
FORMS = [
("0", wow.forms.CharacterCreationForm1),
("1", wow.forms.CharacterCreationForm2),
("2", wow.forms.CharacterCreationForm3),
]
TEMPLATES = {
"0": "wow/character_creation_form_1.html",
"1": "wow/character_creation_form_2.html",
"2": "wow/character_creation_form_3.html",
}
class CharacterWizard(SessionWizardView):
instance = None
#template_name = "wow/character_creation_form_1.html"
# Requires the user to be logged in for every instance of the form wizard
# as-view() *urls.py* creates an instance called dispatch(). Dispatch is used to relay information
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
self.instance = CharacterCreation()
return super(CharacterWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance(self, step):
return self.instance
def get_template_names(self):
#Custom templates for the different steps
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
self.instance.character_creation_user = self.request.user
self.instance.save()
return HttpResponseRedirect('/wow/home/')
Here is my url.py
url(r'^character_creation/$', CharacterWizard.as_view(FORMS), name='character_creation'),
Thank you guys!
In my second and third templates, my form is now action="/character_creation/" instead of /wow/character_creation/.

Django - Need help for TemplateView: Queryset and not updating

I am building a TemplateView with 2 forms, one to allow user to select the customer (CustomerForm) and another to add the order (OrderForm) for the customer.
Code:
class DisplayOrdersView(TemplateView):
template_name = 'orders/orders_details_form.html'
def get_context_data(self, **kwargs):
context = kwargs
context['shippingdetailsform'] = ShippingDetailsForm(prefix='shippingdetailsform')
context['ordersform'] = OrdersForm(prefix='ordersform')
return context
def dispatch(self, request, *args, **kwargs):
return super(DisplayOrdersView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
profile=request.user.get_profile()
if context['shippingdetailsform'].is_valid():
instance = context['shippingdetailsform'].save(commit=False)
instance.profile = profile
instance.save()
messages.success(request, 'orders for {0} saved'.format(profile))
elif context['ordersform'].is_valid():
instance = ordersform.save(commit=False)
shippingdetails, created = shippingdetails.objects.get_or_create(profile=profile)
shippingdetails.save()
instance.user = customer
instance.save()
messages.success(request, 'orders details for {0} saved.'.format(profile))
else:
messages.error(request, 'Error(s) saving form')
return self.render_to_response(context)
Firstly, I can't seem to load any existing data into the forms. Assuming a onetoone relationship between UserProfile->ShippingDetails (fk: UserProfile)->Orders (fk:ShippingDetails), how can I query the appropriate variables into the form on load?
Also, how can I save the data? It throws an error when saving and I have been unable to retrieve useful debug information.
Is my approach correct for having multiple forms in a templateview?
You're not passing the POST data into the forms at any point. You need to do this when you instantiate them. I would move the instantiation out of get_context_data and do it in get and post: the first as you have it now, and the second passing request.POST.
Also note that you probably want to check both forms are valid before saving either of them, rather than checking and saving each in turn. The way you have it now, if the first one is valid it won't even check the second, let alone save it, so you won't get any errors on the template if the first is valid but the second is invalid.

Django best practices - how would you create a page with a form and a list of items the Django-way?

I'm starting with django (and stackoverflow!)
I've been trying to create a webpage with a form and a list of items. ( Django - Mixing ListView and CreateView). I came up with a solution but I'm not convinced by my code!
I'm using Django mixin BaseCreateView and BaseListView to generate the form and list context data. But because it's views, they call directly render_to_response().
So I overloaded the get() method to manually call both parents methods and extract the context data. Then I called render_to_response() myself.
class FormAndListView(BaseCreateView, BaseListView, TemplateResponseMixin):
def get(self, request, *args, **kwargs):
formView = BaseCreateView.get(self, request, *args, **kwargs) # formView contains a response_class
listView = BaseListView.get(self, request, *args, **kwargs) # listView contains a response_class as well...
formData = formView.context_data['form'] # extract the form from the response_class
listData = listView.context_data['object_list'] # extract the object list from the response_class
return render_to_response('textfrompdf/index.html', {'form' : formData, 'all_PDF' : listData},
context_instance=RequestContext(request))
On one hand I'm not re-writing what is already in the mixin to manage forms and lists of items... On the other hand, django is calculating the whole render_to_response() 3 times!
What would be the clean Django-way to write this page?
class FormAndListView(BaseCreateView, BaseListView, TemplateResponseMixin):
def render_to_response(context, **responsekwargs)
return context
def get(self, request, *args, **kwargs):
context = {}
context.update( BaseCreateView.get(self, request, *args, **kwargs) ) # formView contains a response_class
context.update( BaseListView.get(self, request, *args, **kwargs) ) # listView contains a response_class as well...
return TemplateResponseMixin.render_to_response('textfrompdf/index.html', context,
context_instance=RequestContext(request))