Fixes on payments

This commit is contained in:
Marc Aymerich 2015-05-30 14:44:05 +00:00
parent 7c5eff9a90
commit a8cad48fed
29 changed files with 751 additions and 201 deletions

11
TODO.md
View File

@ -413,3 +413,14 @@ touch /tmp/somefile
# inherit registers from parent?
# Disable pagination on membership fees (allways one page)
# datetime metric storage granularity: otherwise innacurate detection of billed metric on order.billed_on
# Serializers.validation migration to DRF3: grep -r 'attrs, source' *|grep -v '~'
serailzer self.instance on create.
# generate Direct debit q19 on a protected path, or store it on the transaction.proc
# regenerate direct debit q19
# add transproc.method for regeneration
# TODO wrapp admin delete: delete proc undo processing on related transactions

View File

@ -14,9 +14,9 @@ from . import settings
class Account(auth.AbstractBaseUser):
# Username max_length determined by LINUX system user lentgh: 32
# Username max_length determined by LINUX system user/group lentgh: 32
username = models.CharField(_("username"), max_length=32, unique=True,
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
help_text=_("Required. 32 characters or fewer. Letters, digits and ./-/_ only."),
validators=[
validators.RegexValidator(r'^[\w.-]+$', _("Enter a valid username."), 'invalid')
])
@ -102,7 +102,7 @@ class Account(auth.AbstractBaseUser):
extra_context.update(context)
with translation.override(self.language):
send_email_template(
template, extra_context, ('marcay@pangea.org',), email_from=email_from, html=html,
template, extra_context, email_to, email_from=email_from, html=html,
attachments=attachments)
def get_full_name(self):

View File

@ -76,8 +76,8 @@ def close_bills(modeladmin, request, queryset):
if num == 1:
url = change_url(transactions[0])
else:
url = reverse('admin:transactions_transaction_changelist')
url += 'id__in=%s' % ','.join(map(str, transactions))
url = reverse('admin:payments_transaction_changelist')
url += 'id__in=%s' % ','.join([str(t.id) for t in transactions])
context = {
'url': url,
'num': num,
@ -109,19 +109,15 @@ close_bills.url_name = 'close'
def send_bills(modeladmin, request, queryset):
num = 0
for bill in queryset:
if not validate_contact(request, bill):
return
num += 1
if num == 1:
bill.send()
else:
# Batch email
queryset.send()
num = 0
for bill in queryset:
bill.send()
modeladmin.log_change(request, bill, 'Sent')
messages.success(request, ungetetx(
num += 1
messages.success(request, ungettext(
_("One bill has been sent."),
_("%i bills have been sent.") % num,
num))

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 09:23+0000\n"
"POT-Creation-Date: 2015-05-29 09:39+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,61 +28,63 @@ msgstr "Vista"
#: actions.py:55
msgid "Selected bills should be in open state"
msgstr ""
msgstr "Les factures seleccionades han d'estar en estat obert"
#: actions.py:73
msgid "Selected bills have been closed"
msgstr ""
msgstr "Les factures seleccionades han estat tancades"
#: actions.py:86
#, python-format
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
msgstr ""
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
#: actions.py:87
#, python-format
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
msgstr ""
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
#: actions.py:93
msgid "Are you sure about closing the following bills?"
msgstr ""
msgstr "Estàs a punt de tancar les següents factures, estàs segur?"
#: actions.py:94
msgid ""
"Once a bill is closed it can not be further modified.</p><p>Please select a "
"payment source for the selected bills"
msgstr ""
"Una vegada la factura estigui tancada no podrà ser modificada.</p><p>Si us "
"plau selecciona un mètode de pagament per les factures seleccionades"
#: actions.py:107
msgid "Close"
msgstr ""
msgstr "Tanca"
#: actions.py:125
msgid "One bill has been sent."
msgstr ""
msgstr "S'ha creat una factura"
#: actions.py:126
#, python-format
msgid "%i bills have been sent."
msgstr ""
msgstr "S'han enviat %i factures."
#: actions.py:128
msgid "Resend"
msgstr ""
msgstr "Reenviat"
#: actions.py:189
#, python-format
msgid "%(norders)s orders and %(nlines)s lines undoed."
msgstr ""
msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
#: actions.py:208
msgid "Lines moved"
msgstr ""
msgstr "Línies mogudes"
#: admin.py:49 admin.py:93 admin.py:128 forms.py:11
msgid "Total"
msgstr ""
msgstr "Total"
#: admin.py:80
msgid "Description"
@ -90,52 +92,52 @@ msgstr "Descripció"
#: admin.py:88
msgid "Subtotal"
msgstr ""
msgstr "Subtotal"
#: admin.py:118
msgid "Is open"
msgstr ""
msgstr "És oberta"
#: admin.py:123
msgid "Subline"
msgstr ""
msgstr "Sublínia"
#: admin.py:157
msgid "No bills selected."
msgstr ""
msgstr "No hi ha factures seleccionades"
#: admin.py:164
#, python-format
msgid "Manage %s bill lines."
msgstr ""
msgstr "Gestiona %s línies de factura."
#: admin.py:166
msgid "Bill not in open state."
msgstr ""
msgstr "La factura no està en estat obert"
#: admin.py:169
msgid "Not all bills are in open state."
msgstr ""
msgstr "No totes les factures estan en estat obert"
#: admin.py:170
msgid "Manage bill lines of multiple bills."
msgstr ""
msgstr "Gestiona línies de factura de multiples factures."
#: admin.py:190
msgid "Raw"
msgstr ""
msgstr "Raw"
#: admin.py:208
msgid "Created"
msgstr ""
msgstr "Creada"
#: admin.py:213
msgid "lines"
msgstr ""
msgstr "línies"
#: admin.py:218 templates/bills/microspective.html:118
msgid "total"
msgstr ""
msgstr "total"
#: admin.py:226 models.py:88 models.py:352
msgid "type"
@ -167,11 +169,11 @@ msgstr "Rectificació de quota de soci"
#: filters.py:22
msgid "Pro-forma"
msgstr ""
msgstr "Pro-forma"
#: filters.py:41
msgid "positive price"
msgstr ""
msgstr "preu positiu"
#: filters.py:46 filters.py:64
msgid "Yes"
@ -183,69 +185,71 @@ msgstr "No"
#: filters.py:59
msgid "has bill contact"
msgstr ""
msgstr "té contacte de facturació"
#: forms.py:9
msgid "Number"
msgstr ""
msgstr "Número"
#: forms.py:10
msgid "Account"
msgstr ""
msgstr "Compte"
#: forms.py:12
msgid "Type"
msgstr ""
msgstr "Tipus"
#: forms.py:13
msgid "Source"
msgstr ""
msgstr "Font"
#: helpers.py:10
msgid ""
"{relation} account \"{account}\" does not have a declared invoice contact. "
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
msgstr ""
"{relation} compte \"{account}\" no te un contacte de facturació. Hauries de "
"<a href=\"{url}#invoicecontact-group\">proporcionar un</a>"
#: helpers.py:17
msgid "Related"
msgstr ""
msgstr "Relacionat"
#: helpers.py:24
msgid "Main"
msgstr ""
msgstr "Principal"
#: models.py:23 models.py:86
msgid "account"
msgstr ""
msgstr "compte"
#: models.py:25
msgid "name"
msgstr ""
msgstr "nom"
#: models.py:26
msgid "Account full name will be used when left blank."
msgstr ""
msgstr "S'emprarà el nom complet del compte quan es deixi en blanc."
#: models.py:27
msgid "address"
msgstr ""
msgstr "adreça"
#: models.py:28
msgid "city"
msgstr ""
msgstr "ciutat"
#: models.py:30
msgid "zip code"
msgstr ""
msgstr "codi postal"
#: models.py:31
msgid "Enter a valid zipcode."
msgstr ""
msgstr "Introdueix un codi postal vàlid."
#: models.py:32
msgid "country"
msgstr ""
msgstr "país"
#: models.py:35
msgid "VAT number"
@ -253,51 +257,51 @@ msgstr "NIF"
#: models.py:67
msgid "Paid"
msgstr ""
msgstr "Pagat"
#: models.py:68
msgid "Pending"
msgstr ""
msgstr "Pendent"
#: models.py:69
msgid "Bad debt"
msgstr ""
msgstr "Incobrable"
#: models.py:81
msgid "Amendment Fee"
msgstr ""
msgstr "Rectificació de quota de soci"
#: models.py:82
msgid "Pro forma"
msgstr ""
msgstr "Pro forma"
#: models.py:85
msgid "number"
msgstr ""
msgstr "número"
#: models.py:89
msgid "created on"
msgstr ""
msgstr "creat el"
#: models.py:90
msgid "closed on"
msgstr ""
msgstr "tancat el"
#: models.py:91
msgid "open"
msgstr ""
msgstr "obert"
#: models.py:92
msgid "sent"
msgstr ""
msgstr "enviat"
#: models.py:93
msgid "due on"
msgstr ""
msgstr "es deu"
#: models.py:94
msgid "updated on"
msgstr ""
msgstr "actualitzada el"
#: models.py:97
msgid "comments"
@ -305,11 +309,11 @@ msgstr "comentaris"
#: models.py:98
msgid "HTML"
msgstr ""
msgstr "HTML"
#: models.py:285
msgid "bill"
msgstr ""
msgstr "factura"
#: models.py:286 models.py:350 templates/bills/microspective.html:73
msgid "description"
@ -332,7 +336,7 @@ msgstr "quantitat"
#: models.py:290 templates/bills/microspective.html:77
#: templates/bills/microspective.html:111
msgid "subtotal"
msgstr ""
msgstr "subtotal"
#: models.py:291
msgid "tax"
@ -340,67 +344,69 @@ msgstr "impostos"
#: models.py:292
msgid "start"
msgstr ""
msgstr "iniciar"
#: models.py:293
msgid "end"
msgstr ""
msgstr "finalitzar"
#: models.py:295
msgid "Informative link back to the order"
msgstr ""
msgstr "Enllaç informatiu de l'ordre"
#: models.py:296
msgid "order billed"
msgstr ""
msgstr "ordre facturada"
#: models.py:297
msgid "order billed until"
msgstr ""
msgstr "ordre facturada fins a"
#: models.py:298
msgid "created"
msgstr ""
msgstr "creada"
#: models.py:300
msgid "amended line"
msgstr ""
msgstr "línia rectificada"
#: models.py:343
msgid "Volume"
msgstr ""
msgstr "Volum"
#: models.py:344
msgid "Compensation"
msgstr ""
msgstr "Compensació"
#: models.py:345
msgid "Other"
msgstr ""
msgstr "Altre"
#: models.py:349
msgid "bill line"
msgstr ""
msgstr "línia de factura"
#: templates/bills/microspective-fee.html:107
msgid "Due date"
msgstr ""
msgstr "Data de pagament"
#: templates/bills/microspective-fee.html:108
#, python-format
msgid "On %(bank_account)s"
msgstr ""
msgstr "Al %(bank_account)s"
#: templates/bills/microspective-fee.html:114
#, python-format
msgid "From %(ini)s to %(end)s"
msgstr ""
msgstr "De %(ini)s a %(end)s"
#: templates/bills/microspective-fee.html:121
msgid ""
"\n"
"<strong>With your membership</strong> you are supporting ...\n"
msgstr ""
"\n"
"<strong>Amb la teva quota de soci</strong> estàs donant suport ...\n"
#: templates/bills/microspective.html:49
msgid "DUE DATE"
@ -411,18 +417,17 @@ msgid "TOTAL"
msgstr "TOTAL"
#: templates/bills/microspective.html:57
#, fuzzy, python-format
#| msgid "%(bill_type|upper)s DATE "
#, python-format
msgid "%(bill_type)s DATE"
msgstr "DATA %(bill_type|upper)s"
msgstr "DATA %(bill_type)s"
#: templates/bills/microspective.html:74
msgid "period"
msgstr ""
msgstr "període"
#: templates/bills/microspective.html:75
msgid "hrs/qty"
msgstr "hrs/quant"
msgstr "hrs/qnt"
#: templates/bills/microspective.html:76
msgid "rate/price"
@ -446,15 +451,6 @@ msgid "PAYMENT"
msgstr "PAGAMENT"
#: templates/bills/microspective.html:140
#, fuzzy, python-format
#| msgid ""
#| "\n"
#| " You can pay our <i>%(type)s</i> by bank transfer. "
#| "<br>\n"
#| " Please make sure to state your name and the <i>"
#| "%(type)s</i> number.\n"
#| " Our bank account number is <br>\n"
#| " "
msgid ""
"\n"
" You can pay our <i>%(type)s</i> by bank transfer.<br>\n"
@ -464,8 +460,8 @@ msgid ""
" "
msgstr ""
"\n"
"Pots pagar aquesta <i>%(type)s</i> per transferencia banacaria.<br>Inclou el "
"teu nom i el numero de <i>%(type)s</i>. El nostre compte bancari és"
"Pots pagar aquesta <i>%(type)s</i> per transferència bancaria.<br>Inclou el "
"teu nom i el número de <i>%(type)s</i>. El nostre compte bancari és"
#: templates/bills/microspective.html:149
msgid "QUESTIONS"
@ -481,3 +477,9 @@ msgid ""
" your message.\n"
" "
msgstr ""
"\n"
" Si tens algun dubte o pregunta sobre la teva <i>%(type)s</i>, si "
"us plau\n"
" contacta amb nosaltres a %(email)s. Et respondrem el més "
"ràpidament possible.\n"
" "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 09:23+0000\n"
"POT-Creation-Date: 2015-05-28 12:31+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -20,47 +20,49 @@ msgstr ""
#: actions.py:37
msgid "Download"
msgstr ""
msgstr "Descarga"
#: actions.py:47
msgid "View"
msgstr ""
msgstr "Vista"
#: actions.py:55
msgid "Selected bills should be in open state"
msgstr ""
msgstr "Las facturas seleccionadas están en estado abierto"
#: actions.py:73
msgid "Selected bills have been closed"
msgstr ""
msgstr "Las facturas seleccionadas han sido cerradas"
#: actions.py:86
#, python-format
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
msgstr ""
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
#: actions.py:87
#, python-format
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
msgstr ""
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
#: actions.py:93
msgid "Are you sure about closing the following bills?"
msgstr ""
msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?"
#: actions.py:94
msgid ""
"Once a bill is closed it can not be further modified.</p><p>Please select a "
"payment source for the selected bills"
msgstr ""
"Una vez cerrada la factura ya no se podrá modificar.</p><p>Por favor "
"seleciona un metodo de pago para las facturas seleccionadas"
#: actions.py:107
msgid "Close"
msgstr ""
msgstr "Cerrar"
#: actions.py:125
msgid "One bill has been sent."
msgstr ""
msgstr "Se ha enviado una factura"
#: actions.py:126
#, python-format
@ -143,7 +145,7 @@ msgstr ""
#: admin.py:243
msgid "Payment"
msgstr ""
msgstr "Pago"
#: filters.py:17
msgid "All"
@ -151,19 +153,19 @@ msgstr ""
#: filters.py:18 models.py:78
msgid "Invoice"
msgstr ""
msgstr "Factura"
#: filters.py:19 models.py:79
msgid "Amendment invoice"
msgstr ""
msgstr "Factura rectificative"
#: filters.py:20 models.py:80
msgid "Fee"
msgstr ""
msgstr "Quota de socio"
#: filters.py:21
msgid "Amendment fee"
msgstr ""
msgstr "Quota rectificativa"
#: filters.py:22
msgid "Pro-forma"
@ -382,17 +384,17 @@ msgstr ""
#: templates/bills/microspective-fee.html:107
msgid "Due date"
msgstr ""
msgstr "Fecha de pago"
#: templates/bills/microspective-fee.html:108
#, python-format
msgid "On %(bank_account)s"
msgstr ""
msgstr "En %(bank_account)s"
#: templates/bills/microspective-fee.html:114
#, python-format
msgid "From %(ini)s to %(end)s"
msgstr ""
msgstr "Desde %(ini)s hasta %(end)s"
#: templates/bills/microspective-fee.html:121
msgid ""
@ -402,45 +404,45 @@ msgstr ""
#: templates/bills/microspective.html:49
msgid "DUE DATE"
msgstr ""
msgstr "VENCIMIENTO"
#: templates/bills/microspective.html:53
msgid "TOTAL"
msgstr ""
msgstr "TOTAL"
#: templates/bills/microspective.html:57
#, python-format
msgid "%(bill_type)s DATE"
msgstr ""
msgstr "FECHA %(bill_type)s"
#: templates/bills/microspective.html:74
msgid "period"
msgstr ""
msgstr "periodo"
#: templates/bills/microspective.html:75
msgid "hrs/qty"
msgstr ""
msgstr "hrs/cant"
#: templates/bills/microspective.html:76
msgid "rate/price"
msgstr ""
msgstr "tarifa/precio"
#: templates/bills/microspective.html:111
#: templates/bills/microspective.html:114
msgid "VAT"
msgstr ""
msgstr "IVA"
#: templates/bills/microspective.html:114
msgid "taxes"
msgstr ""
msgstr "impuestos"
#: templates/bills/microspective.html:130
msgid "COMMENTS"
msgstr ""
msgstr "COMENTARIOS"
#: templates/bills/microspective.html:136
msgid "PAYMENT"
msgstr ""
msgstr "PAGO"
#: templates/bills/microspective.html:140
#, python-format
@ -452,6 +454,9 @@ msgid ""
" Our bank account number is <br>\n"
" "
msgstr ""
"\n"
"Puedes pagar esta <i>%(type)s</i> por transferencia bancaria.<br>Incloye tu "
"nombre y el número de <i>%(type)s</i>. Nuestra cuenta bancaria es"
#: templates/bills/microspective.html:149
msgid "QUESTIONS"
@ -467,3 +472,9 @@ msgid ""
" your message.\n"
" "
msgstr ""
"\n"
" Si tienes alguna duda o pregunta sobre tu <i>%(type)s</i>, por "
"favor\n"
" contacta con nosotros en %(email)s. Te responderemos lo más "
"rapidamente posible.\n"
" "

View File

@ -142,7 +142,7 @@ class Bill(models.Model):
def get_type(self):
return self.type or self.get_class_type()
def set_number(self):
def get_number(self):
cls = type(self)
bill_type = self.get_type()
if bill_type == self.BILL:
@ -162,7 +162,7 @@ class Bill(models.Model):
number_length = settings.BILLS_NUMBER_LENGTH
zeros = (number_length - len(str(number))) * '0'
number = zeros + str(number)
self.number = '{prefix}{year}{number}'.format(prefix=prefix, year=year, number=number)
return '{prefix}{year}{number}'.format(prefix=prefix, year=year, number=number)
def get_due_date(self, payment=None):
now = timezone.now()
@ -178,13 +178,14 @@ class Bill(models.Model):
if not self.due_on:
self.due_on = self.get_due_date(payment=payment)
self.total = self.get_total()
self.html = self.render(payment=payment)
transaction = None
if self.get_type() != self.PROFORMA:
transaction = self.transactions.create(bill=self, source=payment, amount=self.total)
self.closed_on = timezone.now()
self.is_open = False
self.is_sent = False
self.number = self.get_number()
self.html = self.render(payment=payment)
self.save()
return transaction
@ -207,37 +208,37 @@ class Bill(models.Model):
self.save(update_fields=['is_sent'])
def render(self, payment=False, language=None):
if payment is False:
payment = self.account.paymentsources.get_default()
context = Context({
'bill': self,
'lines': self.lines.all().prefetch_related('sublines'),
'seller': self.seller,
'buyer': self.buyer,
'seller_info': {
'phone': settings.BILLS_SELLER_PHONE,
'website': settings.BILLS_SELLER_WEBSITE,
'email': settings.BILLS_SELLER_EMAIL,
'bank_account': settings.BILLS_SELLER_BANK_ACCOUNT,
},
'currency': settings.BILLS_CURRENCY,
'payment': payment and payment.get_bill_context(),
'default_due_date': self.get_due_date(payment=payment),
'now': timezone.now(),
})
template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template)
with translation.override(language or self.account.language):
if payment is False:
payment = self.account.paymentsources.get_default()
context = Context({
'bill': self,
'lines': self.lines.all().prefetch_related('sublines'),
'seller': self.seller,
'buyer': self.buyer,
'seller_info': {
'phone': settings.BILLS_SELLER_PHONE,
'website': settings.BILLS_SELLER_WEBSITE,
'email': settings.BILLS_SELLER_EMAIL,
'bank_account': settings.BILLS_SELLER_BANK_ACCOUNT,
},
'currency': settings.BILLS_CURRENCY,
'payment': payment and payment.get_bill_context(),
'default_due_date': self.get_due_date(payment=payment),
'now': timezone.now(),
})
template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template)
html = bill_template.render(context)
html = html.replace('-pageskip-', '<pdf:nextpage />')
html = html.replace('-pageskip-', '<pdf:nextpage />')
return html
def save(self, *args, **kwargs):
if not self.type:
self.type = self.get_type()
if not self.number or (self.number.startswith('O') and not self.is_open):
self.set_number()
if not self.number:
self.number = self.get_number()
super(Bill, self).save(*args, **kwargs)
def get_subtotals(self):

View File

@ -175,12 +175,12 @@ a:hover {
}
#lines .column-description {
width: 40%;
width: 39%;
text-align: left;
}
#lines .column-period {
width: 22%;
width: 23%;
}
#lines .column-quantity {

View File

@ -135,7 +135,7 @@
<div id="payment">
<span class="title">{% trans "PAYMENT" %}</span>
{% if payment.message %}
{{ payment.message | safe }}
{{ payment.message|safe }}
{% else %}
{% blocktrans with type=bill.get_type_display.lower %}
You can pay our <i>{{ type }}</i> by bank transfer.<br>

View File

@ -12,11 +12,10 @@ class EmailBackend(BaseEmailBackend):
"""
A wrapper that manages a queued SMTP system.
"""
messages = 0
def send_messages(self, email_messages):
if not email_messages:
return
# Count messages per request
cache = get_request_cache()
key = 'mailer.sent_messages'
sent_messages = cache.get(key) or 0
@ -24,7 +23,7 @@ class EmailBackend(BaseEmailBackend):
cache.set(key, sent_messages)
is_bulk = len(email_messages) > 1
if sent_messages > settings.MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD:
if sent_messages > settings.MAILER_NON_QUEUED_PER_REQUEST_THRESHOLD:
is_bulk = True
default_priority = Message.NORMAL if is_bulk else Message.CRITICAL
num_sent = 0

View File

@ -14,6 +14,8 @@ from .models import Message
def send_message(message, num=0, connection=None, bulk=100):
if not message.pk:
message.save()
if num >= bulk:
connection.close()
connection = None

View File

@ -50,7 +50,7 @@ class Message(models.Model):
def sent(self):
self.state = self.SENT
self.save()
self.save(update_fiields=('state',))
def log(self, error):
result = SMTPLog.SUCCESS

View File

@ -13,7 +13,7 @@ MAILER_MESSAGES_CLEANUP_DAYS = Setting('MAILER_MESSAGES_CLEANUP_DAYS',
)
MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD = Setting('MAILER_NON_QUEUED_MAILS_PER_REQUEST_THRESHOLD',
MAILER_NON_QUEUED_PER_REQUEST_THRESHOLD = Setting('MAILER_NON_QUEUED_PER_REQUEST_THRESHOLD',
2,
help_text=_("Number of emails that will be sent directly before starting to queue them."),
help_text=_("Number of emails that will be sent immediately before starting to queue them."),
)

View File

@ -1,6 +1,7 @@
from functools import partial
from django.contrib import messages
from django.contrib.admin import actions
from django.db import transaction
from django.shortcuts import render
from django.utils.safestring import mark_safe
@ -18,23 +19,25 @@ from .models import Transaction
def process_transactions(modeladmin, request, queryset):
processes = []
if queryset.exclude(state=Transaction.WAITTING_PROCESSING).exists():
msg = _("Selected transactions must be on '{state}' state")
messages.error(request, msg.format(state=Transaction.WAITTING_PROCESSING))
messages.error(request,
_("Selected transactions must be on '{state}' state").format(
state=Transaction.WAITTING_PROCESSING)
)
return
for method, transactions in queryset.group_by('source__method').items():
if method is not None:
method = PaymentMethod.get(method)
procs = method.process(transactions)
processes += procs
for trans in transactions:
modeladmin.log_change(request, trans, _("Processed"))
for transaction in transactions:
modeladmin.log_change(request, transaction, _("Processed"))
if not processes:
return
opts = modeladmin.model._meta
num = len(queryset)
context = {
'title': ungettext(
_("Selected transaction has been processed."),
_("One selected transaction has been processed."),
_("%s Selected transactions have been processed.") % num,
num),
'content_message': ungettext(
@ -54,9 +57,9 @@ def process_transactions(modeladmin, request, queryset):
@transaction.atomic
@action_with_confirmation()
def mark_as_executed(modeladmin, request, queryset):
for trans in queryset:
trans.mark_as_executed()
modeladmin.log_change(request, trans, _("Executed"))
for transaction in queryset:
transaction.mark_as_executed()
modeladmin.log_change(request, transaction, _("Executed"))
num = len(queryset)
msg = ungettext(
_("One selected transaction has been marked as executed."),
@ -70,9 +73,9 @@ mark_as_executed.verbose_name = _("Mark as executed")
@transaction.atomic
@action_with_confirmation()
def mark_as_secured(modeladmin, request, queryset):
for trans in queryset:
trans.mark_as_secured()
modeladmin.log_change(request, trans, _("Secured"))
for transaction in queryset:
transaction.mark_as_secured()
modeladmin.log_change(request, transaction, _("Secured"))
num = len(queryset)
msg = ungettext(
_("One selected transaction has been marked as secured."),
@ -86,9 +89,9 @@ mark_as_secured.verbose_name = _("Mark as secured")
@transaction.atomic
@action_with_confirmation()
def mark_as_rejected(modeladmin, request, queryset):
for trans in queryset:
trans.mark_as_rejected()
modeladmin.log_change(request, trans, _("Rejected"))
for transaction in queryset:
transaction.mark_as_rejected()
modeladmin.log_change(request, transaction, _("Rejected"))
num = len(queryset)
msg = ungettext(
_("One selected transaction has been marked as rejected."),
@ -157,9 +160,9 @@ abort.verbose_name = _("Abort")
@transaction.atomic
@action_with_confirmation(extra_context=_format_commit)
def commit(modeladmin, request, queryset):
for trans in queryset:
trans.mark_as_rejected()
modeladmin.log_change(request, trans, _("Rejected"))
for transaction in queryset:
transaction.mark_as_rejected()
modeladmin.log_change(request, transaction, _("Rejected"))
num = len(queryset)
msg = ungettext(
_("One selected transaction has been marked as rejected."),
@ -168,3 +171,29 @@ def commit(modeladmin, request, queryset):
modeladmin.message_user(request, msg)
commit.url_name = 'commit'
commit.verbose_name = _("Commit")
def delete_selected(modeladmin, request, queryset):
""" Has to have same name as admin.actions.delete_selected """
if not queryset:
messages.warning(request, "No transaction process selected.")
return
if queryset.exclude(transactions__state=Transaction.WAITTING_EXECUTION).exists():
messages.error(request, "Done nothing. Not all related transactions in waitting execution.")
return
# Store before deleting
related_transactions = []
for process in queryset:
related_transactions.extend(process.transactions.filter(state=Transaction.WAITTING_EXECUTION))
response = actions.delete_selected(modeladmin, request, queryset)
if response is None:
# Confirmation
num = 0
for transaction in related_transactions:
transaction.state = Transaction.WAITTING_PROCESSING
transaction.save(update_fields=('state',))
num += 1
modeladmin.log_change(request, transaction, _("Unprocessed"))
messages.success(request, "%i related transactions marked as waitting for processing." % num)
return response
delete_selected.short_description = actions.delete_selected.short_description

View File

@ -122,8 +122,8 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
fields = ('data', 'file_url', 'created_at')
readonly_fields = ('data', 'file_url', 'display_transactions', 'created_at')
inlines = [TransactionInline]
actions = (actions.mark_process_as_executed, actions.abort, actions.commit)
change_view_actions = actions
change_view_actions = (actions.mark_process_as_executed, actions.abort, actions.commit)
actions = change_view_actions + (actions.delete_selected,)
def file_url(self, process):
if process.file:
@ -138,7 +138,7 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
# Because of values_list this query doesn't benefit from prefetch_related
tx_ids = process.transactions.values_list('id', flat=True)
for tx_id in tx_ids:
ids.append('#%i' % tx_id)
ids.append(str(tx_id))
counter += 1
if counter > 10:
counter = 0

View File

@ -0,0 +1,250 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:21
msgid "Selected transactions must be on '{state}' state"
msgstr ""
#: actions.py:30
msgid "Processed"
msgstr ""
#: actions.py:37
msgid "Selected transaction has been processed."
msgstr ""
#: actions.py:38
#, python-format
msgid "%s Selected transactions have been processed."
msgstr ""
#: actions.py:41
msgid ""
"The following transaction process has been generated, you may want to save "
"it on your computer now."
msgstr ""
#: actions.py:43
#, python-format
msgid ""
"The following %s transaction processes have been generated, you may want to "
"save it on your computer now."
msgstr ""
#: actions.py:46
msgid "Process"
msgstr ""
#: actions.py:59 actions.py:130 models.py:93 models.py:161
msgid "Executed"
msgstr ""
#: actions.py:62
msgid "One selected transaction has been marked as executed."
msgstr ""
#: actions.py:63
#, python-format
msgid "%s selected transactions have been marked as executed."
msgstr ""
#: actions.py:67 actions.py:138
msgid "Mark as executed"
msgstr ""
#: actions.py:75 models.py:94
msgid "Secured"
msgstr ""
#: actions.py:78
msgid "One selected transaction has been marked as secured."
msgstr ""
#: actions.py:79
#, python-format
msgid "%s selected transactions have been marked as secured."
msgstr ""
#: actions.py:83
msgid "Mark as secured"
msgstr ""
#: actions.py:91 actions.py:162 models.py:95
msgid "Rejected"
msgstr ""
#: actions.py:94 actions.py:165
msgid "One selected transaction has been marked as rejected."
msgstr ""
#: actions.py:95 actions.py:166
#, python-format
msgid "%s selected transactions have been marked as rejected."
msgstr ""
#: actions.py:99
msgid "Mark as rejected"
msgstr ""
#: actions.py:133
msgid "One selected process has been marked as executed."
msgstr ""
#: actions.py:134
#, python-format
msgid "%s selected processes have been marked as executed."
msgstr ""
#: actions.py:146 models.py:162
msgid "Aborted"
msgstr ""
#: actions.py:149
msgid "One selected process has been aborted."
msgstr ""
#: actions.py:150
#, python-format
msgid "%s selected processes have been aborted."
msgstr ""
#: actions.py:154
msgid "Abort"
msgstr ""
#: actions.py:170
msgid "Commit"
msgstr ""
#: admin.py:42
msgid "ID"
msgstr ""
#: admin.py:101
msgid "proc"
msgstr ""
#: admin.py:152
msgid "Transactions"
msgstr ""
#: methods/creditcard.py:11
msgid "Label"
msgstr ""
#: methods/creditcard.py:12
msgid "Use a name such as \"Jo's Visa\" to remember which card it is."
msgstr ""
#: methods/creditcard.py:30
msgid "Credit card"
msgstr ""
#: methods/sepadirectdebit.py:23 methods/sepadirectdebit.py:30
msgid "Name"
msgstr ""
#: methods/sepadirectdebit.py:39
msgid "SEPA Direct Debit"
msgstr ""
#: methods/sepadirectdebit.py:48
msgid ""
"This bill will be automatically charged to your bank account with IBAN "
"number<br><strong>%s</strong>."
msgstr ""
"Aquesta factura es cobrarà automaticament en el teu compte bancari amb IBAN "
"<br><strong>%s</strong>."
#: models.py:19
msgid "account"
msgstr ""
#: models.py:21
msgid "method"
msgstr ""
#: models.py:23 models.py:166
msgid "data"
msgstr ""
#: models.py:24
msgid "active"
msgstr ""
#: models.py:91
msgid "Waitting processing"
msgstr ""
#: models.py:92
msgid "Waitting execution"
msgstr ""
#: models.py:98
msgid "bill"
msgstr ""
#: models.py:101
msgid "source"
msgstr ""
#: models.py:103
msgid "process"
msgstr ""
#: models.py:104 models.py:168
msgid "state"
msgstr ""
#: models.py:106
msgid "amount"
msgstr ""
#: models.py:108 models.py:169
msgid "created"
msgstr ""
#: models.py:109
msgid "modified"
msgstr ""
#: models.py:124
msgid "New transactions can not be allocated for this bill."
msgstr ""
#: models.py:160
msgid "Created"
msgstr ""
#: models.py:163
msgid "Commited"
msgstr ""
#: models.py:167
msgid "file"
msgstr ""
#: models.py:170
msgid "updated"
msgstr ""
#: models.py:173
msgid "Transaction processes"
msgstr ""

View File

@ -0,0 +1,250 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:21
msgid "Selected transactions must be on '{state}' state"
msgstr ""
#: actions.py:30
msgid "Processed"
msgstr ""
#: actions.py:37
msgid "Selected transaction has been processed."
msgstr ""
#: actions.py:38
#, python-format
msgid "%s Selected transactions have been processed."
msgstr ""
#: actions.py:41
msgid ""
"The following transaction process has been generated, you may want to save "
"it on your computer now."
msgstr ""
#: actions.py:43
#, python-format
msgid ""
"The following %s transaction processes have been generated, you may want to "
"save it on your computer now."
msgstr ""
#: actions.py:46
msgid "Process"
msgstr ""
#: actions.py:59 actions.py:130 models.py:93 models.py:161
msgid "Executed"
msgstr ""
#: actions.py:62
msgid "One selected transaction has been marked as executed."
msgstr ""
#: actions.py:63
#, python-format
msgid "%s selected transactions have been marked as executed."
msgstr ""
#: actions.py:67 actions.py:138
msgid "Mark as executed"
msgstr ""
#: actions.py:75 models.py:94
msgid "Secured"
msgstr ""
#: actions.py:78
msgid "One selected transaction has been marked as secured."
msgstr ""
#: actions.py:79
#, python-format
msgid "%s selected transactions have been marked as secured."
msgstr ""
#: actions.py:83
msgid "Mark as secured"
msgstr ""
#: actions.py:91 actions.py:162 models.py:95
msgid "Rejected"
msgstr ""
#: actions.py:94 actions.py:165
msgid "One selected transaction has been marked as rejected."
msgstr ""
#: actions.py:95 actions.py:166
#, python-format
msgid "%s selected transactions have been marked as rejected."
msgstr ""
#: actions.py:99
msgid "Mark as rejected"
msgstr ""
#: actions.py:133
msgid "One selected process has been marked as executed."
msgstr ""
#: actions.py:134
#, python-format
msgid "%s selected processes have been marked as executed."
msgstr ""
#: actions.py:146 models.py:162
msgid "Aborted"
msgstr ""
#: actions.py:149
msgid "One selected process has been aborted."
msgstr ""
#: actions.py:150
#, python-format
msgid "%s selected processes have been aborted."
msgstr ""
#: actions.py:154
msgid "Abort"
msgstr ""
#: actions.py:170
msgid "Commit"
msgstr ""
#: admin.py:42
msgid "ID"
msgstr ""
#: admin.py:101
msgid "proc"
msgstr ""
#: admin.py:152
msgid "Transactions"
msgstr ""
#: methods/creditcard.py:11
msgid "Label"
msgstr ""
#: methods/creditcard.py:12
msgid "Use a name such as \"Jo's Visa\" to remember which card it is."
msgstr ""
#: methods/creditcard.py:30
msgid "Credit card"
msgstr ""
#: methods/sepadirectdebit.py:23 methods/sepadirectdebit.py:30
msgid "Name"
msgstr ""
#: methods/sepadirectdebit.py:39
msgid "SEPA Direct Debit"
msgstr ""
#: methods/sepadirectdebit.py:48
msgid ""
"This bill will be automatically charged to your bank account with IBAN "
"number<br><strong>%s</strong>."
msgstr ""
"Esta factura se cobrará automaticamente en tu cuenta bancaria con IBAN "
"<br><strong>%s</strong>."
#: models.py:19
msgid "account"
msgstr ""
#: models.py:21
msgid "method"
msgstr ""
#: models.py:23 models.py:166
msgid "data"
msgstr ""
#: models.py:24
msgid "active"
msgstr ""
#: models.py:91
msgid "Waitting processing"
msgstr ""
#: models.py:92
msgid "Waitting execution"
msgstr ""
#: models.py:98
msgid "bill"
msgstr ""
#: models.py:101
msgid "source"
msgstr ""
#: models.py:103
msgid "process"
msgstr ""
#: models.py:104 models.py:168
msgid "state"
msgstr ""
#: models.py:106
msgid "amount"
msgstr ""
#: models.py:108 models.py:169
msgid "created"
msgstr ""
#: models.py:109
msgid "modified"
msgstr ""
#: models.py:124
msgid "New transactions can not be allocated for this bill."
msgstr ""
#: models.py:160
msgid "Created"
msgstr ""
#: models.py:163
msgid "Commited"
msgstr ""
#: models.py:167
msgid "file"
msgstr ""
#: models.py:170
msgid "updated"
msgstr ""
#: models.py:173
msgid "Transaction processes"
msgstr ""

View File

@ -45,7 +45,7 @@ class SEPADirectDebit(PaymentMethod):
due_delta = datetime.timedelta(days=5)
def get_bill_message(self):
return _("This bill will been automatically charged to your bank account "
return _("This bill will be automatically charged to your bank account "
" with IBAN number<br><strong>%s</strong>.") % self.instance.number
@classmethod
@ -211,7 +211,7 @@ class SEPADirectDebit(PaymentMethod):
),
E.DbtrAcct( # Debtor Account
E.Id(
E.IBAN(data['iban'])
E.IBAN(data['iban'].replace(' ', ''))
),
),
)
@ -246,7 +246,7 @@ class SEPADirectDebit(PaymentMethod):
),
E.CdtrAcct( # Creditor Account
E.Id(
E.IBAN(data['iban'])
E.IBAN(data['iban'].replace(' ', ''))
),
),
)
@ -279,7 +279,8 @@ class SEPADirectDebit(PaymentMethod):
xsd_path = os.path.join(path, xsd)
schema_doc = etree.parse(xsd_path)
schema = etree.XMLSchema(schema_doc)
sepa = etree.parse(StringIO(etree.tostring(sepa)))
sepa = StringIO(etree.tostring(sepa).decode('utf8'))
sepa = etree.parse(sepa)
schema.assertValid(sepa)
process.file = file_name
process.save(update_fields=['file'])

View File

@ -99,7 +99,7 @@ class Transaction(models.Model):
related_name='transactions')
source = models.ForeignKey(PaymentSource, null=True, blank=True,
verbose_name=_("source"), related_name='transactions')
process = models.ForeignKey('payments.TransactionProcess', null=True,
process = models.ForeignKey('payments.TransactionProcess', null=True, on_delete=models.SET_NULL,
blank=True, verbose_name=_("process"), related_name='transactions')
state = models.CharField(_("state"), max_length=32, choices=STATES,
default=WAITTING_PROCESSING)
@ -111,7 +111,7 @@ class Transaction(models.Model):
objects = TransactionQuerySet.as_manager()
def __str__(self):
return "Transaction #{}".format(self.id)
return "#%i" % self.id
@property
def account(self):

View File

@ -6,7 +6,7 @@
<p>{{ content_message }}</p>
<ul>
{% for proc in processes %}
<li> <a href="{{ proc.id }}">Process #{{ proc.id }}</a>
<li> <a href="{% url admin:payments_transactionprocess_change' proc.pk|admin_urlquote %}">Process #{{ proc.id }}</a>
{% if proc.file %}
<ul><li>File: <a href="{{ proc.file.url }}">{{ proc.file }}</a></li></ul>
{% endif %}

View File

@ -94,4 +94,3 @@ def delete_selected(modeladmin, request, queryset):
return
return admin.actions.delete_selected(modeladmin, request, queryset)
delete_selected.short_description = _("Delete selected %(verbose_name_plural)s")

View File

@ -41,8 +41,8 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
add_form = SystemUserCreationForm
form = SystemUserChangeForm
ordering = ('-id',)
actions = (delete_selected, set_permission, disable)
change_view_actions = actions
change_view_actions = (set_permission, disable)
actions = (delete_selected,) + change_view_actions
def display_main(self, user):
return user.is_main

View File

@ -31,10 +31,10 @@ class SystemUser(models.Model):
"""
System users
Username max_length determined by LINUX system user lentgh: 32
Username max_length determined by LINUX system user/group lentgh: 32
"""
username = models.CharField(_("username"), max_length=32, unique=True,
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
help_text=_("Required. 32 characters or fewer. Letters, digits and ./-/_ only."),
validators=[validators.validate_username])
password = models.CharField(_("password"), max_length=128)
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),

View File

@ -7,7 +7,6 @@ def html_to_pdf(html, pagination=False):
""" converts HTL to PDF using wkhtmltopdf """
context = {
'pagination': textwrap.dedent("""\
--footer-center "Page [page] of [topage]"\\
--footer-center "Page [page] of [topage]" \\
--footer-font-name sans \\
--footer-font-size 7 \\
@ -20,6 +19,6 @@ def html_to_pdf(html, pagination=False):
--use-xserver \\
%(pagination)s \\
--margin-bottom 22 \\
--margin-top 20 - -\
--margin-top 20 - - \
""") % context
return run(cmd, stdin=html.encode('utf-8')).stdout

View File

@ -15,8 +15,6 @@ def render_email_template(template, context):
"""
if isinstance(context, dict):
context = Context(context)
if isinstance(to, str):
to = [to]
if not 'site' in context:
from orchestra import settings
@ -32,6 +30,8 @@ def render_email_template(template, context):
def send_email_template(template, context, to, email_from=None, html=None, attachments=[]):
if isinstance(to, str):
to = [to]
subject, message = render_email_template(template, context)
msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments)
if html:

View File

@ -10,7 +10,7 @@ celery==3.1.16
kombu==3.0.23
billiard==3.3.0.18
Markdown==2.4
djangorestframework==3.1.1
djangorestframework==3.1.2
ecdsa==0.11
Pygments==1.6
django-filter==0.7