diff --git a/TODO.md b/TODO.md
index d2bc862b..57e0b36a 100644
--- a/TODO.md
+++ b/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.
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
+
diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py
index 6adaf842..53d65d81 100644
--- a/orchestra/admin/options.py
+++ b/orchestra/admin/options.py
@@ -1,10 +1,9 @@
from django import forms
from django.conf.urls import patterns, url
from django.contrib import admin
+from django.contrib.admin.utils import unquote
from django.forms.models import BaseInlineFormSet
-from orchestra.utils.functional import cached
-
from .utils import set_url_query, action_to_view
@@ -59,12 +58,11 @@ class ChangeViewActionsMixin(object):
url('^(\d+)/%s/$' % action.url_name,
admin_site.admin_view(action),
name='%s_%s_%s' % (opts.app_label,
- opts.module_name,
+ opts.model_name,
action.url_name)))
return new_urls + urls
- @cached
- def get_change_view_actions(self):
+ def get_change_view_actions(self, obj=None):
views = []
for action in self.change_view_actions:
if isinstance(action, basestring):
@@ -75,16 +73,18 @@ class ChangeViewActionsMixin(object):
view.url_name.capitalize().replace('_', ' '))
view.css_class = getattr(action, 'css_class', 'historylink')
view.description = getattr(action, 'description', '')
+ view.__name__ = action.__name__
views.append(view)
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:
kwargs['extra_context'] = {}
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):
diff --git a/orchestra/apps/bills/actions.py b/orchestra/apps/bills/actions.py
index 5161f2c3..4a800ed7 100644
--- a/orchestra/apps/bills/actions.py
+++ b/orchestra/apps/bills/actions.py
@@ -1,13 +1,48 @@
+import StringIO
+import zipfile
+
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.close()
- 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)
+ pdf = html_to_pdf(bill.html)
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'
diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py
index 643ed8af..e6fbf3f3 100644
--- a/orchestra/apps/bills/admin.py
+++ b/orchestra/apps/bills/admin.py
@@ -1,7 +1,9 @@
from django import forms
from django.contrib import admin
+#from django.contrib.admin.utils import unquote
from django.core.urlresolvers import reverse
from django.db import models
+#from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
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 . import settings
-from .actions import generate_bill
+from .actions import render_bills, download_bills, view_bill, close_bills
from .filters import BillTypeListFilter
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget,
BillLine, BudgetLine)
@@ -66,7 +68,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
'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')
readonly_fields = ('number', 'display_total')
inlines = [BillLineInline]
@@ -97,6 +100,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
fields += self.add_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):
if self.model is Budget:
self.inlines = [BudgetLineInline]
@@ -112,12 +122,20 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
- def queryset(self, request):
- qs = super(BillAdmin, self).queryset(request)
+ def get_queryset(self, request):
+ qs = super(BillAdmin, self).get_queryset(request)
qs = qs.annotate(models.Count('billlines'))
qs = qs.prefetch_related('billlines', 'billlines__sublines')
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(Invoice, BillAdmin)
diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py
index 33a902bf..6e5680a3 100644
--- a/orchestra/apps/bills/models.py
+++ b/orchestra/apps/bills/models.py
@@ -118,7 +118,7 @@ class Bill(models.Model):
def render(self):
context = Context({
'bill': self,
- 'lines': self.lines.all(),
+ 'lines': self.lines.all().prefetch_related('sublines'),
'seller': self.seller,
'buyer': self.buyer,
'seller_info': {
@@ -145,7 +145,7 @@ class Bill(models.Model):
@cached
def get_subtotals(self):
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 += line.total
for subline in line.sublines.all():
@@ -155,6 +155,7 @@ class Bill(models.Model):
@cached
def get_total(self):
+ # TODO self.total = self.get_total on self.save()
total = 0
for tax, subtotal in self.get_subtotals().iteritems():
subtotal, taxes = subtotal
diff --git a/orchestra/apps/bills/templates/bills/microspective.css b/orchestra/apps/bills/templates/bills/microspective.css
index bfd5b68d..f04ba7c3 100644
--- a/orchestra/apps/bills/templates/bills/microspective.css
+++ b/orchestra/apps/bills/templates/bills/microspective.css
@@ -1,7 +1,8 @@
body {
/* max-width: 650px;*/
- max-width: 800px;
+ max-width: 670px;
margin: 40 auto !important;
+/* margin-bottom: 30 !important;*/
float: none !important;
font-family: Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif;
}
@@ -34,7 +35,7 @@ a:hover {
font-size: 20;
font-weight: bold;
color: grey;
- margin-top: 15px;
+ margin-top: 30px;
margin-bottom: 10px;
}
@@ -44,11 +45,9 @@ a:hover {
font-weight: normal;
}
-#pagination {
- font-size: small;
-}
/* SUMMARY */
+
#bill-summary {
clear: right;
}
@@ -113,6 +112,10 @@ a:hover {
margin: 40px;
}
+#seller-details {
+ margin-top: 0px;
+}
+
#seller-details p {
margin-top: 5px;
}
@@ -158,10 +161,14 @@ a:hover {
color: {{ color }};
}
-#lines .value {
+#lines .last {
border-bottom: 1px solid #CCC;
}
+#lines .subline {
+ padding-top: 0px;
+}
+
#lines .column-id {
width: 5%;
text-align: right;
@@ -230,27 +237,32 @@ a:hover {
/* FOOTER */
+.content {
+ display: table-row; /* height is dynamic, and will expand... */
+ height: 100%; /* ...as content is added (won't scroll) */
+}
.wrapper {
- min-height: 100%;
- height: auto !important;
+ display: table;
height: 100%;
- margin: 0 auto -4em;
+ width: 100%;
}
-#footer, .push {
- height: 4em;
+.footer {
+ display: table-row;
}
-#footer .title {
+.footer .title {
color: {{ color }};
font-weight: bold;
}
-#footer > * > * {
+.footer > * > * {
margin: 5px;
+ margin-bottom: 8px;
color: #666;
font-size: small;
+ text-align: justify;
}
#footer-column-1 {
@@ -262,3 +274,7 @@ a:hover {
float: right;
width: 48%;
}
+
+#questions {
+ margin-bottom: 0px;
+}
diff --git a/orchestra/apps/bills/templates/bills/microspective.html b/orchestra/apps/bills/templates/bills/microspective.html
index 981cd10f..54d96dc0 100644
--- a/orchestra/apps/bills/templates/bills/microspective.html
+++ b/orchestra/apps/bills/templates/bills/microspective.html
@@ -10,6 +10,7 @@
{% block body %}
+
{% block header %}
{% block logo %}
@@ -40,7 +41,6 @@
{{ bill.get_type_display }}
{{ bill.number }}
-
@@ -74,13 +74,23 @@
rate/price
subtotal
- {% for line in bill.lines.all %}
- {{ line.id }}
- {{ line.description }}
- {{ line.amount|default:" " }}
- {% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %} {% endif %}
- {{ line.total }} &{{ currency.lower }};
+ {% for line in lines %}
+ {% with sublines=line.sublines.all %}
+ {{ line.id }}
+ {{ line.description }}
+ {{ line.amount|default:" " }}
+ {% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %} {% endif %}
+ {{ line.total }} &{{ currency.lower }};
+ {% for subline in sublines %}
+
+ {{ subline.description }}
+
+
+ {{ subline.total }} &{{ currency.lower }};
+
+ {% endfor %}
+ {% endwith %}
{% endfor %}
@@ -100,9 +110,8 @@
{% endblock %}
{% block footer %}
-
-