Added pdf rendering support

This commit is contained in:
Marc 2014-08-29 12:45:27 +00:00
parent ef7f3219a5
commit b4113ef770
16 changed files with 61 additions and 41 deletions

View file

@ -28,9 +28,6 @@ class Command(BaseCommand):
email = options.get('email') email = options.get('email')
username = options.get('username') username = options.get('username')
password = options.get('password') password = options.get('password')
user = User.objects.create_superuser(username, email, password, account=account, account = Account.objects.create()
is_main=True) user = User.objects.create_superuser(username, email, password,
account = Account.objects.create(user=user) account=account, is_main=True)
user.account = account
user.save()

View file

@ -1,7 +1,11 @@
from django.http import HttpResponse from django.http import HttpResponse
from orchestra.utils.system import run
def generate_bill(modeladmin, request, queryset): def generate_bill(modeladmin, request, queryset):
bill = queryset.get() bill = queryset.get()
bill.close() bill.close()
return HttpResponse(bill.html) pdf = run('xvfb-run -a -s "-screen 0 640x4800x16" wkhtmltopdf - -',
stdin=bill.html.encode('utf-8'), display=False)
return HttpResponse(pdf, content_type='application/pdf')

View file

@ -16,7 +16,7 @@ class BillManager(models.Manager):
def get_queryset(self): def get_queryset(self):
queryset = super(BillManager, self).get_queryset() queryset = super(BillManager, self).get_queryset()
if self.model != Bill: if self.model != Bill:
bill_type = self.model.get_type() bill_type = self.model.get_class_type()
queryset = queryset.filter(type=bill_type) queryset = queryset.filter(type=bill_type)
return queryset return queryset
@ -127,7 +127,8 @@ class Bill(models.Model):
}, },
'currency': settings.BILLS_CURRENCY, 'currency': settings.BILLS_CURRENCY,
}) })
template = getattr(settings, 'BILLS_%s_TEMPLATE' % self.get_type()) template = getattr(settings, 'BILLS_%s_TEMPLATE' % self.get_type(),
settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template) bill_template = loader.get_template(template)
html = bill_template.render(context) html = bill_template.render(context)
html = html.replace('-pageskip-', '<pdf:nextpage />') html = html.replace('-pageskip-', '<pdf:nextpage />')

View file

@ -14,7 +14,11 @@ BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBE
BILLS_BUDGET_NUMBER_PREFIX = getattr(settings, 'BILLS_BUDGET_NUMBER_PREFIX', 'Q') BILLS_BUDGET_NUMBER_PREFIX = getattr(settings, 'BILLS_BUDGET_NUMBER_PREFIX', 'Q')
BILLS_INVOICE_TEMPLATE = getattr(settings, 'BILLS_INVOICE_TEMPLATE', 'bills/microspective.html') BILLS_DEFAULT_TEMPLATE = getattr(settings, 'BILLS_DEFAULT_TEMPLATE', 'bills/microspective.html')
BILLS_FEE_TEMPLATE = getattr(settings, 'BILLS_FEE_TEMPLATE', 'bills/microspective-fee.html')
BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY', 'euro') BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY', 'euro')

View file

@ -36,6 +36,7 @@
float: right; float: right;
padding: 15px; padding: 15px;
margin: 10px; margin: 10px;
margin-top: 0px;
width: 44%; width: 44%;
font-size: large; font-size: large;
} }
@ -84,7 +85,7 @@ hr {
{% endblock %} {% endblock %}
{% block summary %} {% block summary %}
<div style="position: relative; margin-top: 150px;"> <div style="position: relative; margin-top: 140px;">
<hr> <hr>
</div> </div>
@ -121,5 +122,8 @@ from Apr 1, 2010 to Apr 1, 2011
Con vuestras cuotas, ademas de obtener <br> Con vuestras cuotas, ademas de obtener <br>
</div> </div>
{% endblock %} {% endblock %}
{% block pagination %}<hr>{% endblock %} {% block footer %}
<hr>
{{ block.super }}
{% endblock %}

View file

@ -1,5 +1,6 @@
body { body {
max-width: 650px; /* max-width: 650px;*/
max-width: 800px;
margin: 40 auto !important; margin: 40 auto !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;
@ -48,14 +49,20 @@ a:hover {
} }
/* SUMMARY */ /* SUMMARY */
#bill-summary {
clear: right;
}
#bill-summary > * { #bill-summary > * {
float: right; float: right;
border: 1px solid grey; border: 1px solid grey;
padding: 7px; padding: 7px 12px 7px 12px;
text-align: center; text-align: center;
font-size: large; font-size: large;
width: 100px; width: 100px;
overflow: hidden;
white-space: nowrap;
} }
#bill-summary hr { #bill-summary hr {
@ -161,12 +168,12 @@ a:hover {
} }
#lines .column-description { #lines .column-description {
width: 55%; width: 65%;
text-align: left; text-align: left;
} }
#lines .column-quantity { #lines .column-quantity {
width: 20%; width: 10%;
} }
#lines .column-rate { #lines .column-rate {

View file

@ -12,7 +12,7 @@
{% block header %} {% block header %}
<div id="logo"> <div id="logo">
{% block logo %} {% block logo %}
<div style="border-bottom:5px solid grey; color:grey; font-size:30;"> <div style="border-bottom:5px solid grey; color:grey; font-size:30; margin-right: 20px;">
YOUR<br> YOUR<br>
LOGO<br> LOGO<br>
HERE<br> HERE<br>
@ -76,8 +76,8 @@
{% for line in bill.lines.all %} {% for line in bill.lines.all %}
<span class="value column-id">{{ line.id }}</span> <span class="value column-id">{{ line.id }}</span>
<span class="value column-description">{{ line.description }}</span> <span class="value column-description">{{ line.description }}</span>
<span class="value column-quantity">{{ line.amount }}</span> <span class="value column-quantity">{{ line.amount|default:"&nbsp;" }}</span>
<span class="value column-rate">{{ line.rate }}</span> <span class="value column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %}&nbsp;{% endif %}</span>
<span class="value column-subtotal">{{ line.price }} &{{ currency.lower }};</span> <span class="value column-subtotal">{{ line.price }} &{{ currency.lower }};</span>
<br> <br>
{% endfor %} {% endfor %}

View file

@ -70,7 +70,8 @@ class MailboxAdmin(AccountAdminMixin, ExtendedModelAdmin):
add_url += '?account=%d&mailboxes=%s' % (account.pk, mailbox.pk) add_url += '?account=%d&mailboxes=%s' % (account.pk, mailbox.pk)
img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">' img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">'
onclick = 'onclick="return showAddAnotherPopup(this);"' onclick = 'onclick="return showAddAnotherPopup(this);"'
add_link = '<a href="%s" %s>%s Add address</a>' % (add_url, onclick, img) add_link = '<a href="{add_url}" {onclick}>{img} Add address</a>'.format(
add_url=add_url, onclick=onclick, img=img)
value = '%s<br><br>' % add_link value = '%s<br><br>' % add_link
for pk, name, domain in mailbox.addresses.values_list('pk', 'name', 'domain__name'): for pk, name, domain in mailbox.addresses.values_list('pk', 'name', 'domain__name'):
url = reverse('admin:mails_address_change', args=(pk,)) url = reverse('admin:mails_address_change', args=(pk,))

View file

@ -282,6 +282,9 @@ class Order(models.Model):
if service.handler.matches(instance): if service.handler.matches(instance):
if not orders: if not orders:
account_id = getattr(instance, 'account_id', instance.pk) account_id = getattr(instance, 'account_id', instance.pk)
if account_id is None:
# New account workaround -> user.account_id == None
continue
order = cls.objects.create(content_object=instance, order = cls.objects.create(content_object=instance,
service=service, account_id=account_id) service=service, account_id=account_id)
else: else:

View file

@ -1,5 +1,4 @@
from .methods import BankTransfer
def process_transactions(modeladmin, request, queryset): def process_transactions(modeladmin, request, queryset):
BankTransfer().process(queryset) from .methods import SEPADirectDebit
SEPADirectDebit().process(queryset)

View file

@ -6,7 +6,7 @@ from orchestra.admin.utils import admin_colored, admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin
from .actions import process_transactions from .actions import process_transactions
from .methods import BankTransfer from .methods import SEPADirectDebit
from .models import PaymentSource, Transaction, PaymentProcess from .models import PaymentSource, Transaction, PaymentProcess
@ -39,7 +39,7 @@ class TransactionAdmin(admin.ModelAdmin):
class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin): class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
list_display = ('label', 'method', 'number', 'account_link', 'is_active') list_display = ('label', 'method', 'number', 'account_link', 'is_active')
list_filter = ('method', 'is_active') list_filter = ('method', 'is_active')
form = BankTransfer().get_form() form = SEPADirectDebit().get_form()
# TODO select payment source method # TODO select payment source method

View file

@ -1,3 +1,3 @@
from .creditcard import CreditCard from .creditcard import CreditCard
from .banktransfer import BankTransfer from .sepadirectdebit import SEPADirectDebit
from .options import PaymentMethod, PaymentSourceDataForm from .options import PaymentMethod, PaymentSourceDataForm

View file

@ -15,26 +15,26 @@ from .. import settings
from .options import PaymentSourceDataForm, PaymentMethod from .options import PaymentSourceDataForm, PaymentMethod
class BankTransferForm(PaymentSourceDataForm): class SEPADirectDebitForm(PaymentSourceDataForm):
iban = IBANFormField(label='IBAN', iban = IBANFormField(label='IBAN',
widget=forms.TextInput(attrs={'size': '50'})) widget=forms.TextInput(attrs={'size': '50'}))
name = forms.CharField(max_length=128, label=_("Name"), name = forms.CharField(max_length=128, label=_("Name"),
widget=forms.TextInput(attrs={'size': '50'})) widget=forms.TextInput(attrs={'size': '50'}))
class BankTransferSerializer(serializers.Serializer): class SEPADirectDebitSerializer(serializers.Serializer):
iban = serializers.CharField(label='IBAN', validators=[IBANValidator()], iban = serializers.CharField(label='IBAN', validators=[IBANValidator()],
min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34) min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34)
name = serializers.CharField(label=_("Name"), max_length=128) name = serializers.CharField(label=_("Name"), max_length=128)
class BankTransfer(PaymentMethod): class SEPADirectDebit(PaymentMethod):
verbose_name = _("Bank transfer") verbose_name = _("Direct Debit")
label_field = 'name' label_field = 'name'
number_field = 'iban' number_field = 'iban'
process_credit = True process_credit = True
form = BankTransferForm form = SEPADirectDebitForm
serializer = BankTransferSerializer serializer = SEPADirectDebitSerializer
def process(self, transactions): def process(self, transactions):
debts = [] debts = []

View file

@ -72,7 +72,7 @@ class Transaction(models.Model):
return "Transaction {}".format(self.id) return "Transaction {}".format(self.id)
# TODO rename to TransactionProcess # TODO rename to TransactionProcess or PaymentRequest TransactionRequest
class PaymentProcess(models.Model): class PaymentProcess(models.Model):
""" """
Stores arbitrary data generated by payment methods while processing transactions Stores arbitrary data generated by payment methods while processing transactions

View file

@ -127,7 +127,9 @@ function install_requirements () {
bind9utils \ bind9utils \
python-cracklib \ python-cracklib \
libxml2-dev \ libxml2-dev \
libxslt1-dev" libxslt1-dev \
wkhtmltopdf \
xvfb"
PIP="django==1.6.1 \ PIP="django==1.6.1 \
django-celery-email==1.0.4 \ django-celery-email==1.0.4 \
@ -151,8 +153,7 @@ function install_requirements () {
if $testing; then if $testing; then
APT="${APT} \ APT="${APT} \
iceweasel \ iceweasel"
xvfb"
PIP="${PIP} \ PIP="${PIP} \
selenium \ selenium \
xvfbwrapper" xvfbwrapper"

View file

@ -46,21 +46,20 @@ def read_async(fd):
return '' return ''
def run(command, display=True, error_codes=[0], silent=True): def run(command, display=True, error_codes=[0], silent=True, stdin=''):
""" Subprocess wrapper for running commands """ """ Subprocess wrapper for running commands """
if display: if display:
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command) sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
out_stream = subprocess.PIPE
err_stream = subprocess.PIPE
p = subprocess.Popen(command, shell=True, executable='/bin/bash', p = subprocess.Popen(command, shell=True, executable='/bin/bash',
stdout=out_stream, stderr=err_stream) stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
make_async(p.stdout) make_async(p.stdout)
make_async(p.stderr) make_async(p.stderr)
stdout = str() stdout = str()
stderr = str() stderr = str()
p.stdin.write(stdin)
p.stdin.close()
# Async reading of stdout and sterr # Async reading of stdout and sterr
while True: while True:
# Wait for data to become available # Wait for data to become available