Django: Adding attachment to generic.DetailView - django

I have a existing Django App with a detailview for UserProfiles. Now our client wants to have the ability to 'download' the information of the page to a PDF.
I have added a button in the HTML to trigger the 'generate-attachement' method
<div class="input-group">
<button name='zip' value="True" type="submit">Get report</button>
</div>
I have also added a 'generate_pdf' method to the view, which is triggered by the button above.
class ProfileView(ProfileMixin, generic.DetailView):
template_name = 'user/profile/detail.html'
def get_object(self, queryset=None):
return self.context.profile_user
def generate_pdf(self):
from reportlab.pdfgen import canvas
response = HttpResponse(content_type='application/pdf')
response['pdf'] = 'attachment; filename="summary.pdf"'
p = canvas.Canvas(response)
p.drawString(100, 100, "Hello world.")
p.showPage()
p.save()
print(p)
return response
def get_context_data(self, **kwargs):
data = super(ProfileView, self).get_context_data(**kwargs)
#Check if 'get attachement' button has been pressed
if self.request.GET.get('zip', None):
self.generate_pdf()
#Code to load relevant data form a large number of models
#append it to the 'data' variable .
#(e.g data['group_year] = ...etc
return data
However, when I run this code / press the button the method / print commands are all triggered, but no attachment is returned to the browser
<reportlab.pdfgen.canvas.Canvas instance at 0x112165638>
[08/Feb/2018 12:30:08] "GET /user/profile/459/?zip=True HTTP/1.1" 200 41749
Yet I got most of the code from the official Django Documentation, so its not entirely clear to me why my code is failing.
Does anyone have an idea of what I am doing wrong?

You need to override get() method of your view to customize response:
class ProfileView(ProfileMixin, generic.DetailView):
template_name = 'user/profile/detail.html'
def get(self, request, *args, **kwargs):
#Check if 'get attachement' button has been pressed
if self.request.GET.get('zip', None):
return self.generate_pdf()
return super(ProfileView, self).get(request, *args, **kwargs)

looks like you should have used 'Content-Disposition' rather than 'pdf' to add your file to the response.
response['Content-Disposition'] = 'attachment; filename="summary.pdf"'

Related

Django-Admin : Override add-URL of the Foreign Key Auto-Complete "add" button / + green cross

Is there a simple way of overriding/pass a parameter to the popup url automatically created by django-admin built-in autocomplete/select2 widget to create a new foreign key object? This url is embedded in the green cross (see picture).
I didn't come across any well described solutions.
So the default url is pointing towards admin/app/model/add/?_to_field=id&_popup=1 but I would like to add a parameter admin/app/model/add/?_to_field=id&_popup=1&field_1=100 in order to pre-populate some fieds on the popup add_view.
Any leads?
As per Maxim's answer :
def formfield_for_dbfield(self, db_field, request, **kwargs):
if db_field.name == "clinical_exam":
widget = super(ConsultationAdmin, self).formfield_for_dbfield(db_field, request, **kwargs).widget
widget.template_name = 'admin/related_widget_wrapper_with_id.html'
widget.attrs.update({'your_parameter': 'you can use it after in template'})
print(db_field)
return db_field.formfield(widget=widget)
return super(ConsultationAdmin, self).formfield_for_dbfield(db_field, request, **kwargs)
'change + -' this is a result of render RelatedFieldWidgetWrapper
from django.contrib.admin.widgets
You can change attributes of this widget, but it is a little bit complicated.
in ModelAdmin.formfield_for_dbfield:
def formfield_for_dbfield(self, *args, **kwargs):
widget = super.formfield_for_dbfield(self, *args, **kwargs).widget
if isinstance(widget, RelatedFieldWidgetWrapper):
old_context = widget.get_context
widget.get_context = my_function(widget.get_context, *args, **kwargs)
my function is a decorator for old function:
def my_function(func, *initargs, **initkwargs):
def wrapped(*args, **kwargs):
context = func(*args, **kwargs)
your_keys_vals_to_add_in_url = 'something in form key=val&'
context['url_params'] = f'{context['url_params']}&{your_keys_vals_to_add_in_url}'
return context
return wrapped
Other possibility is - to change template related_widget_wrapper.html
from django.admin.contrib.templates.admin.widgets
You can hardcoded your values there.
in ModelAdmin.formfield_for_dbfield:
def formfield_for_dbfield(self, *args, **kwargs):
widget = super.formfield_for_dbfield(self, *args, **kwargs).widget
if isinstance(widget, RelatedFieldWidgetWrapper):
widget.template_name = 'path/to/your_overriden_related_widget_wrapper.html'
widget.attrs.update('your_parameter' : 'you can use it after in template') # this is not work right now in django
and after it in own template:
# example for change link
# this is not work right now in django
<a ... data-href-template="{{ change_related_template_url }}?{{ url_params }}&{{ attr.your_parameter|dafault:'' }}" .. >
The last part not works in Django right now. I made an issue in Django project about this possibility.

How to redirect to a url when using modal forms in django

Having a model (ProductSerialNumbers) which simulates a product with a serial number and after a successful implementation of the CRUD cycle with the aid of modal fm forms https://github.com/django-fm/django-fm
i face up the problem of unsuccessful redirection to other url after a successful delete of the object. The app keeps showing the modal fm window after clicking ok(for delete).
How can i solve that to redirect to my list of ProductSerialNumbers?
Here is my code of the view and a part of my template concerning the modal-delete.
view
class ProductSerialNumbersDeleteView(LoginRequiredMixin, AjaxDeleteView):
model = ProductSerialNumbers
success_url = reverse_lazy('warehouse_stuffing')
'''Function for deleting only the ProductSerialNuumbers which belong to an order, otherwise can not delete '''
# Patch queryset to get the productSerialNumber
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
if (self.object.order):
print("It belongs to an order, do not delete")
return redirect('/warehouse_stuffing/')
else:
print("It is not in an order,delete")
self.object.delete()
print(self.success_url)
return redirect('/warehouse_stuffing/')
seems that the redirect() function is not working properly.
template
<td>Delete</button></td>
The view is called by ajax, so the redirect is returned to and processed by javascript, which obviously won't result in a redirect of the page.
If you want to redirect, you should:
set data-fm-callback to redirect_from_response or redirect, as described in the documentation
and change your view to send the redirect url in the response (or since it's static just hardcode it in your data-fm-target attribute and the view returns just the status 'ok': self.render_json_response(self.get_success_result())).
Finally after searching I came out with this solution:
view.py
class ProductSerialNumbersDeleteView(LoginRequiredMixin, AjaxDeleteView):
model = ProductSerialNumbers
success_url = reverse_lazy('warehouse_stuffing')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
if (self.object.order):
return self.render_json_response(self.get_success_result())
else:
self.object.delete()
return self.render_json_response(self.get_success_result())
and in my template as far as the fm-delete is concerned the structure is:
<td><button class="btn btn-danger btn-sm" type="">Delete</button></td>
in summary the return must be:
return self.render_json_response(self.get_success_result())
and the modal delete must be:
data-fm-callback="redirect" data-fm-target="{% url 'url_name' %}"

Change status code of Django CBV response if form is not valid

Using Django's class-based views, how can I set the status code to 400 when returning a response if the form is not valid? This is easy enough in a functional view, but the CBV responds w/ a 200 status regardless of form validity.
class CRMContactsAdd(CreateView):
model = models.Contact
fields = ['name','job_title','phone_number','email_address','notes']
template_name = 'crm/add_contact.html'
def get_success_url(self):
return reverse('CRM-list')+"?show_modal_url="+reverse('CRM-contacts',args=())
def get_context_data(self, **kwargs):
cd = super(CRMContactsAdd,self).get_context_data(**kwargs)
cd['company'] = get_object_or_404(models.Company,pk=self.kwargs['pk'])
cd['title'] = "Add contact to %s" % (cd['company'].name)
return cd
def form_valid(self, form):
contact = form.save(commit=False)
contact.company = get_object_or_404(models.Company,pk=self.kwargs['pk'])
return super(CRMContactsAdd,self).form_valid(form)
The reason why I'd want to do this is that I can easily use status codes to say whether or not, with a form in a modal window, reload the top level window or update the modal w/ the response of submitting the form.
You can override form_invalid() to change the status code of the response:
class CRMContactsAdd(CreateView):
def form_invalid(self, form):
response = super().form_invalid(form)
response.status_code = 400
return response

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/.

Refreshing tab content in openstack django webapp

I am facing a weird issue in Openstack webapp where i am trying to refresh the tabbed content, however i end up appending the refreshed tab content to the the HTML DOM.
The flow of the code is simple except that i donot understand how the HTML is being attached to DOM instead of the existing tab.
The code flow is:
foo.get --->TabView.get --> TabView.handle_tabbed_response.
I desire that the tab be updated and not the DOM,
What have i done:
Class foo((tabs.TabView):
tab_group_class = (barTabs)
template_name = 'project/xyz/index.html'
def get(self, request, *args, **kwargs):
######## Business Logic
thelper = business_logic(request)
thelper.add_data(request)
return super(foo, self).get(request, *args, **kwargs)
def get_initial(self):
initial = super(foo, self).get_initial()
return initial
class TabView(generic.TemplateView):
"""
A generic class-based view for displaying a :class:`horizon.tabs.TabGroup`.
This view handles selecting specific tabs and deals with AJAX requests
gracefully.
.. attribute:: tab_group_class
The only required attribute for ``TabView``. It should be a class which
inherits from :class:`horizon.tabs.TabGroup`.
"""
tab_group_class = None
_tab_group = None
def __init__(self):
if not self.tab_group_class:
raise AttributeError("You must set the tab_group_class attribute "
"on %s." % self.__class__.__name__)
def get_tabs(self, request, **kwargs):
""" Returns the initialized tab group for this view. """
if self._tab_group is None:
self._tab_group = self.tab_group_class(request, **kwargs)
return self._tab_group
def get_context_data(self, **kwargs):
""" Adds the ``tab_group`` variable to the context data. """
context = super(TabView, self).get_context_data(**kwargs)
try:
tab_group = self.get_tabs(self.request, **kwargs)
context["tab_group"] = tab_group
# Make sure our data is pre-loaded to capture errors.
context["tab_group"].load_tab_data()
except Exception:
exceptions.handle(self.request)
return context
def handle_tabbed_response(self, tab_group, context):
"""
Sends back an AJAX-appropriate response for the tab group if
required, otherwise renders the response as normal.
"""
if self.request.is_ajax():
if tab_group.selected:
return http.HttpResponse(tab_group.selected.render())
else:
return http.HttpResponse(tab_group.render())
return self.render_to_response(context)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.handle_tabbed_response(context["tab_group"], context)
def render_to_response(self, *args, **kwargs):
response = super(TabView, self).render_to_response(*args, **kwargs)
# Because Django's TemplateView uses the TemplateResponse class
# to provide deferred rendering (which is usually helpful), if
# a tab group raises an Http302 redirect (from exceptions.handle for
# example) the exception is actually raised *after* the final pass
# of the exception-handling middleware.
response.render()
return response
Please help.