Added pdf rendering support
This commit is contained in:
parent
ef7f3219a5
commit
b4113ef770
|
@ -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()
|
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 />')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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:" " }}</span>
|
||||||
<span class="value column-rate">{{ line.rate }}</span>
|
<span class="value column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %} {% 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 %}
|
||||||
|
|
|
@ -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,))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = []
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue