Added support for bill sublines on microspective template0
This commit is contained in:
parent
5cfb48f8df
commit
1f00b27667
1
TODO.md
1
TODO.md
|
@ -78,3 +78,4 @@ at + clock time, midnight, noon- At 3:30 p.m., At 4:01, At noon
|
||||||
* make account_link to autoreplace account on change view.
|
* make account_link to autoreplace account on change view.
|
||||||
|
|
||||||
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
|
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin.utils import unquote
|
||||||
from django.forms.models import BaseInlineFormSet
|
from django.forms.models import BaseInlineFormSet
|
||||||
|
|
||||||
from orchestra.utils.functional import cached
|
|
||||||
|
|
||||||
from .utils import set_url_query, action_to_view
|
from .utils import set_url_query, action_to_view
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,12 +58,11 @@ class ChangeViewActionsMixin(object):
|
||||||
url('^(\d+)/%s/$' % action.url_name,
|
url('^(\d+)/%s/$' % action.url_name,
|
||||||
admin_site.admin_view(action),
|
admin_site.admin_view(action),
|
||||||
name='%s_%s_%s' % (opts.app_label,
|
name='%s_%s_%s' % (opts.app_label,
|
||||||
opts.module_name,
|
opts.model_name,
|
||||||
action.url_name)))
|
action.url_name)))
|
||||||
return new_urls + urls
|
return new_urls + urls
|
||||||
|
|
||||||
@cached
|
def get_change_view_actions(self, obj=None):
|
||||||
def get_change_view_actions(self):
|
|
||||||
views = []
|
views = []
|
||||||
for action in self.change_view_actions:
|
for action in self.change_view_actions:
|
||||||
if isinstance(action, basestring):
|
if isinstance(action, basestring):
|
||||||
|
@ -75,16 +73,18 @@ class ChangeViewActionsMixin(object):
|
||||||
view.url_name.capitalize().replace('_', ' '))
|
view.url_name.capitalize().replace('_', ' '))
|
||||||
view.css_class = getattr(action, 'css_class', 'historylink')
|
view.css_class = getattr(action, 'css_class', 'historylink')
|
||||||
view.description = getattr(action, 'description', '')
|
view.description = getattr(action, 'description', '')
|
||||||
|
view.__name__ = action.__name__
|
||||||
views.append(view)
|
views.append(view)
|
||||||
return views
|
return views
|
||||||
|
|
||||||
def change_view(self, *args, **kwargs):
|
def change_view(self, request, object_id, **kwargs):
|
||||||
|
obj = self.get_object(request, unquote(object_id))
|
||||||
if not 'extra_context' in kwargs:
|
if not 'extra_context' in kwargs:
|
||||||
kwargs['extra_context'] = {}
|
kwargs['extra_context'] = {}
|
||||||
kwargs['extra_context']['object_tools_items'] = [
|
kwargs['extra_context']['object_tools_items'] = [
|
||||||
action.__dict__ for action in self.get_change_view_actions()
|
action.__dict__ for action in self.get_change_view_actions(obj)
|
||||||
]
|
]
|
||||||
return super(ChangeViewActionsMixin, self).change_view(*args, **kwargs)
|
return super(ChangeViewActionsMixin, self).change_view(request, object_id, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ChangeAddFieldsMixin(object):
|
class ChangeAddFieldsMixin(object):
|
||||||
|
|
|
@ -1,13 +1,48 @@
|
||||||
|
import StringIO
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.utils.system import run
|
from orchestra.utils.html import html_to_pdf
|
||||||
|
|
||||||
|
|
||||||
def generate_bill(modeladmin, request, queryset):
|
def render_bills(modeladmin, request, queryset):
|
||||||
|
for bill in queryset:
|
||||||
|
bill.html = bill.render()
|
||||||
|
bill.save()
|
||||||
|
render_bills.verbose_name = _("Render")
|
||||||
|
render_bills.url_name = 'render'
|
||||||
|
|
||||||
|
|
||||||
|
def download_bills(modeladmin, request, queryset):
|
||||||
|
if queryset.count() > 1:
|
||||||
|
stringio = StringIO.StringIO()
|
||||||
|
archive = zipfile.ZipFile(stringio, 'w')
|
||||||
|
for bill in queryset:
|
||||||
|
pdf = html_to_pdf(bill.html)
|
||||||
|
archive.writestr('%s.pdf' % bill.number, pdf)
|
||||||
|
archive.close()
|
||||||
|
response = HttpResponse(stringio.getvalue(), content_type='application/pdf')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
|
||||||
|
return response
|
||||||
bill = queryset.get()
|
bill = queryset.get()
|
||||||
bill.close()
|
pdf = html_to_pdf(bill.html)
|
||||||
return HttpResponse(bill.html)
|
|
||||||
pdf = run('xvfb-run -a -s "-screen 0 640x4800x16" '
|
|
||||||
'wkhtmltopdf --footer-center "Page [page] of [topage]" --footer-font-size 9 - -',
|
|
||||||
stdin=bill.html.encode('utf-8'), display=False)
|
|
||||||
return HttpResponse(pdf, content_type='application/pdf')
|
return HttpResponse(pdf, content_type='application/pdf')
|
||||||
|
download_bills.verbose_name = _("Download")
|
||||||
|
download_bills.url_name = 'download'
|
||||||
|
|
||||||
|
|
||||||
|
def view_bill(modeladmin, request, queryset):
|
||||||
|
bill = queryset.get()
|
||||||
|
bill.html = bill.render()
|
||||||
|
return HttpResponse(bill.html)
|
||||||
|
view_bill.verbose_name = _("View")
|
||||||
|
view_bill.url_name = 'view'
|
||||||
|
|
||||||
|
|
||||||
|
def close_bills(modeladmin, request, queryset):
|
||||||
|
for bill in queryset:
|
||||||
|
bill.close()
|
||||||
|
close_bills.verbose_name = _("Close")
|
||||||
|
close_bills.url_name = 'close'
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
#from django.contrib.admin.utils import unquote
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
#from django.shortcuts import redirect
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
|
@ -9,7 +11,7 @@ from orchestra.admin.utils import admin_link, admin_date
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .actions import generate_bill
|
from .actions import render_bills, download_bills, view_bill, close_bills
|
||||||
from .filters import BillTypeListFilter
|
from .filters import BillTypeListFilter
|
||||||
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget,
|
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget,
|
||||||
BillLine, BudgetLine)
|
BillLine, BudgetLine)
|
||||||
|
@ -66,7 +68,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
'fields': ('html',),
|
'fields': ('html',),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
change_view_actions = [generate_bill]
|
actions = [render_bills, download_bills, close_bills]
|
||||||
|
change_view_actions = [render_bills, view_bill, download_bills]
|
||||||
change_readonly_fields = ('account_link', 'type', 'status')
|
change_readonly_fields = ('account_link', 'type', 'status')
|
||||||
readonly_fields = ('number', 'display_total')
|
readonly_fields = ('number', 'display_total')
|
||||||
inlines = [BillLineInline]
|
inlines = [BillLineInline]
|
||||||
|
@ -97,6 +100,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
fields += self.add_fields
|
fields += self.add_fields
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
def get_change_view_actions(self, obj=None):
|
||||||
|
actions = super(BillAdmin, self).get_change_view_actions(obj)
|
||||||
|
if obj and not obj.html:
|
||||||
|
actions = [action for action in actions
|
||||||
|
if action.__name__ not in ('view_bill', 'download_bills')]
|
||||||
|
return actions
|
||||||
|
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
if self.model is Budget:
|
if self.model is Budget:
|
||||||
self.inlines = [BudgetLineInline]
|
self.inlines = [BudgetLineInline]
|
||||||
|
@ -112,12 +122,20 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
|
kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
|
||||||
return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
def queryset(self, request):
|
def get_queryset(self, request):
|
||||||
qs = super(BillAdmin, self).queryset(request)
|
qs = super(BillAdmin, self).get_queryset(request)
|
||||||
qs = qs.annotate(models.Count('billlines'))
|
qs = qs.annotate(models.Count('billlines'))
|
||||||
qs = qs.prefetch_related('billlines', 'billlines__sublines')
|
qs = qs.prefetch_related('billlines', 'billlines__sublines')
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
# def change_view(self, request, object_id, **kwargs):
|
||||||
|
# opts = self.model._meta
|
||||||
|
# if opts.module_name == 'bill':
|
||||||
|
# obj = self.get_object(request, unquote(object_id))
|
||||||
|
# return redirect(
|
||||||
|
# reverse('admin:bills_%s_change' % obj.type.lower(), args=[obj.pk]))
|
||||||
|
# return super(BillAdmin, self).change_view(request, object_id, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Bill, BillAdmin)
|
admin.site.register(Bill, BillAdmin)
|
||||||
admin.site.register(Invoice, BillAdmin)
|
admin.site.register(Invoice, BillAdmin)
|
||||||
|
|
|
@ -118,7 +118,7 @@ class Bill(models.Model):
|
||||||
def render(self):
|
def render(self):
|
||||||
context = Context({
|
context = Context({
|
||||||
'bill': self,
|
'bill': self,
|
||||||
'lines': self.lines.all(),
|
'lines': self.lines.all().prefetch_related('sublines'),
|
||||||
'seller': self.seller,
|
'seller': self.seller,
|
||||||
'buyer': self.buyer,
|
'buyer': self.buyer,
|
||||||
'seller_info': {
|
'seller_info': {
|
||||||
|
@ -145,7 +145,7 @@ class Bill(models.Model):
|
||||||
@cached
|
@cached
|
||||||
def get_subtotals(self):
|
def get_subtotals(self):
|
||||||
subtotals = {}
|
subtotals = {}
|
||||||
for line in self.lines.all():
|
for line in self.lines.all().prefetch_related('sublines'):
|
||||||
subtotal, taxes = subtotals.get(line.tax, (0, 0))
|
subtotal, taxes = subtotals.get(line.tax, (0, 0))
|
||||||
subtotal += line.total
|
subtotal += line.total
|
||||||
for subline in line.sublines.all():
|
for subline in line.sublines.all():
|
||||||
|
@ -155,6 +155,7 @@ class Bill(models.Model):
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def get_total(self):
|
def get_total(self):
|
||||||
|
# TODO self.total = self.get_total on self.save()
|
||||||
total = 0
|
total = 0
|
||||||
for tax, subtotal in self.get_subtotals().iteritems():
|
for tax, subtotal in self.get_subtotals().iteritems():
|
||||||
subtotal, taxes = subtotal
|
subtotal, taxes = subtotal
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
body {
|
body {
|
||||||
/* max-width: 650px;*/
|
/* max-width: 650px;*/
|
||||||
max-width: 800px;
|
max-width: 670px;
|
||||||
margin: 40 auto !important;
|
margin: 40 auto !important;
|
||||||
|
/* margin-bottom: 30 !important;*/
|
||||||
float: none !important;
|
float: none !important;
|
||||||
font-family: Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif;
|
font-family: Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +35,7 @@ a:hover {
|
||||||
font-size: 20;
|
font-size: 20;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: grey;
|
color: grey;
|
||||||
margin-top: 15px;
|
margin-top: 30px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +45,9 @@ a:hover {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pagination {
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SUMMARY */
|
/* SUMMARY */
|
||||||
|
|
||||||
#bill-summary {
|
#bill-summary {
|
||||||
clear: right;
|
clear: right;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +112,10 @@ a:hover {
|
||||||
margin: 40px;
|
margin: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#seller-details {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
#seller-details p {
|
#seller-details p {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
@ -158,10 +161,14 @@ a:hover {
|
||||||
color: {{ color }};
|
color: {{ color }};
|
||||||
}
|
}
|
||||||
|
|
||||||
#lines .value {
|
#lines .last {
|
||||||
border-bottom: 1px solid #CCC;
|
border-bottom: 1px solid #CCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#lines .subline {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
#lines .column-id {
|
#lines .column-id {
|
||||||
width: 5%;
|
width: 5%;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
@ -230,27 +237,32 @@ a:hover {
|
||||||
|
|
||||||
|
|
||||||
/* FOOTER */
|
/* FOOTER */
|
||||||
|
.content {
|
||||||
|
display: table-row; /* height is dynamic, and will expand... */
|
||||||
|
height: 100%; /* ...as content is added (won't scroll) */
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
min-height: 100%;
|
display: table;
|
||||||
height: auto !important;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0 auto -4em;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer, .push {
|
.footer {
|
||||||
height: 4em;
|
display: table-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer .title {
|
.footer .title {
|
||||||
color: {{ color }};
|
color: {{ color }};
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer > * > * {
|
.footer > * > * {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
text-align: justify;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer-column-1 {
|
#footer-column-1 {
|
||||||
|
@ -262,3 +274,7 @@ a:hover {
|
||||||
float: right;
|
float: right;
|
||||||
width: 48%;
|
width: 48%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#questions {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
<div class="content">
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div id="logo">
|
<div id="logo">
|
||||||
{% block logo %}
|
{% block logo %}
|
||||||
|
@ -40,7 +41,6 @@
|
||||||
<div id="bill-number">
|
<div id="bill-number">
|
||||||
{{ bill.get_type_display }}<br>
|
{{ bill.get_type_display }}<br>
|
||||||
<span class="value">{{ bill.number }}</span><br>
|
<span class="value">{{ bill.number }}</span><br>
|
||||||
<span id="pagination">Page 1 of 1</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="bill-summary">
|
<div id="bill-summary">
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -74,13 +74,23 @@
|
||||||
<span class="title column-rate">rate/price</span>
|
<span class="title column-rate">rate/price</span>
|
||||||
<span class="title column-subtotal">subtotal</span>
|
<span class="title column-subtotal">subtotal</span>
|
||||||
<br>
|
<br>
|
||||||
{% for line in bill.lines.all %}
|
{% for line in lines %}
|
||||||
<span class="value column-id">{{ line.id }}</span>
|
{% with sublines=line.sublines.all %}
|
||||||
<span class="value column-description">{{ line.description }}</span>
|
<span class="{% if not sublines %}last {% endif %}column-id">{{ line.id }}</span>
|
||||||
<span class="value column-quantity">{{ line.amount|default:" " }}</span>
|
<span class="{% if not sublines %}last {% endif %}column-description">{{ line.description }}</span>
|
||||||
<span class="value column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %} {% endif %}</span>
|
<span class="{% if not sublines %}last {% endif %}column-quantity">{{ line.amount|default:" " }}</span>
|
||||||
<span class="value column-subtotal">{{ line.total }} &{{ currency.lower }};</span>
|
<span class="{% if not sublines %}last {% endif %}column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %} {% endif %}</span>
|
||||||
|
<span class="{% if not sublines %}last {% endif %}column-subtotal">{{ line.total }} &{{ currency.lower }};</span>
|
||||||
<br>
|
<br>
|
||||||
|
{% for subline in sublines %}
|
||||||
|
<span class="{% if forloop.last %}last {% endif %}subline column-id"> </span>
|
||||||
|
<span class="{% if forloop.last %}last {% endif %}subline column-description">{{ subline.description }}</span>
|
||||||
|
<span class="{% if forloop.last %}last {% endif %}subline column-quantity"> </span>
|
||||||
|
<span class="{% if forloop.last %}last {% endif %}subline column-rate"> </span>
|
||||||
|
<span class="{% if forloop.last %}last {% endif %}subline column-subtotal">{{ subline.total }} &{{ currency.lower }};</span>
|
||||||
|
<br>
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div id="totals">
|
<div id="totals">
|
||||||
|
@ -100,9 +110,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
<div class="push"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div class="footer">
|
||||||
<div id="footer-column-1">
|
<div id="footer-column-1">
|
||||||
<div id="comments">
|
<div id="comments">
|
||||||
{% if bill.comments %}
|
{% if bill.comments %}
|
||||||
|
@ -112,7 +121,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="footer-column-2">
|
<div id="footer-column-2">
|
||||||
<div id="payment">
|
<div id="payment">
|
||||||
<span class="title">PAYMENT</span> {{ bill.payment.message }}
|
<span class="title">PAYMENT</span>
|
||||||
|
{% if bill.payment.message %}
|
||||||
|
{{ bill.payment.message }}
|
||||||
|
{% else %}
|
||||||
|
You can pay our invoice by bank transfer. <br>
|
||||||
|
Please make sure to state your name and the invoice number.
|
||||||
|
Our bank account number is <br>
|
||||||
|
<strong>000-000-000-000 (Orchestra)</strong>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div id="questions">
|
<div id="questions">
|
||||||
<span class="title">QUESTIONS</span> If you have any question about your bill, please
|
<span class="title">QUESTIONS</span> If you have any question about your bill, please
|
||||||
|
@ -121,6 +138,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -236,7 +236,8 @@ class OrderQuerySet(models.QuerySet):
|
||||||
def bill(self, **options):
|
def bill(self, **options):
|
||||||
bills = []
|
bills = []
|
||||||
bill_backend = Order.get_bill_backend()
|
bill_backend = Order.get_bill_backend()
|
||||||
for account, services in self.group_by('account', 'service'):
|
qs = self.select_related('account', 'service')
|
||||||
|
for account, services in qs.group_by('account', 'service'):
|
||||||
bill_lines = []
|
bill_lines = []
|
||||||
for service, orders in services:
|
for service, orders in services:
|
||||||
lines = service.handler.create_bill_lines(orders, **options)
|
lines = service.handler.create_bill_lines(orders, **options)
|
||||||
|
@ -350,6 +351,11 @@ class MetricStorage(models.Model):
|
||||||
cls.objects.create(order=order, value=value)
|
cls.objects.create(order=order, value=value)
|
||||||
else:
|
else:
|
||||||
metric.save()
|
metric.save()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, order, ini, end):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, dispatch_uid="orders.cancel_orders")
|
@receiver(pre_delete, dispatch_uid="orders.cancel_orders")
|
||||||
|
|
|
@ -35,6 +35,8 @@ MEDIA_URL = '/media/'
|
||||||
|
|
||||||
ALLOWED_HOSTS = '*'
|
ALLOWED_HOSTS = '*'
|
||||||
|
|
||||||
|
# Set this to True to wrap each HTTP request in a transaction on this database.
|
||||||
|
ATOMIC_REQUESTS = True
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
'django.middleware.gzip.GZipMiddleware',
|
'django.middleware.gzip.GZipMiddleware',
|
||||||
|
@ -43,7 +45,6 @@ MIDDLEWARE_CLASSES = (
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.transaction.TransactionMiddleware',
|
|
||||||
'orchestra.core.cache.RequestCacheMiddleware',
|
'orchestra.core.cache.RequestCacheMiddleware',
|
||||||
'orchestra.apps.orchestration.middlewares.OperationsMiddleware',
|
'orchestra.apps.orchestration.middlewares.OperationsMiddleware',
|
||||||
# Uncomment the next line for simple clickjacking protection:
|
# Uncomment the next line for simple clickjacking protection:
|
||||||
|
|
8
orchestra/utils/html.py
Normal file
8
orchestra/utils/html.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from orchestra.utils.system import run
|
||||||
|
|
||||||
|
|
||||||
|
def html_to_pdf(html):
|
||||||
|
""" converts HTL to PDF using wkhtmltopdf """
|
||||||
|
return run('xvfb-run -a -s "-screen 0 640x4800x16" '
|
||||||
|
'wkhtmltopdf --footer-center "Page [page] of [topage]" --footer-font-size 9 - -',
|
||||||
|
stdin=html.encode('utf-8'), display=False)
|
Loading…
Reference in a new issue