Random fixes

This commit is contained in:
Marc 2014-10-11 16:21:51 +00:00
parent 1c8ef622b0
commit 920f8efcd5
18 changed files with 215 additions and 112 deletions

View File

@ -90,7 +90,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* mail backend related_models = ('resources__content_type') ?? * mail backend related_models = ('resources__content_type') ??
* ignore orders (mark orders as ignored) * ignore orders (mark orders as ignored), ignore orchestra related orders by default (or do not generate them on the first place) ignore superuser orders?
* Domain backend PowerDNS Bind validation support? * Domain backend PowerDNS Bind validation support?
@ -115,8 +115,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* Separate panel from server passwords? Store passwords on panel? set_password special backend operation? * Separate panel from server passwords? Store passwords on panel? set_password special backend operation?
* be more explicit about which backends are resources and which are service handling
* What fields we really need on contacts? name email phone and what more? * What fields we really need on contacts? name email phone and what more?
* Redirect junk emails and delete every 30 days? * Redirect junk emails and delete every 30 days?

View File

@ -86,8 +86,11 @@ class ChangeViewActionsMixin(object):
action = getattr(self, action) action = getattr(self, action)
view = action_to_view(action, self) view = action_to_view(action, self)
view.url_name = getattr(action, 'url_name', action.__name__) view.url_name = getattr(action, 'url_name', action.__name__)
view.verbose_name = getattr(action, 'verbose_name', verbose_name = getattr(action, 'verbose_name',
view.url_name.capitalize().replace('_', ' ')) view.url_name.capitalize().replace('_', ' '))
if hasattr(verbose_name, '__call__'):
verbose_name = verbose_name(obj)
view.verbose_name = verbose_name
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', '')
views.append(view) views.append(view)
@ -186,7 +189,7 @@ class SelectPluginAdminMixin(object):
def add_view(self, request, form_url='', extra_context=None): def add_view(self, request, form_url='', extra_context=None):
""" Redirects to select account view if required """ """ Redirects to select account view if required """
if request.user.is_superuser: if request.user.is_superuser:
plugin_value = request.GET.get(self.plugin_field) plugin_value = request.GET.get(self.plugin_field) or request.POST.get(self.plugin_field)
if plugin_value or self.plugin.get_plugins() == 1: if plugin_value or self.plugin.get_plugins() == 1:
self.plugin_value = plugin_value self.plugin_value = plugin_value
if not plugin_value: if not plugin_value:

View File

@ -4,37 +4,17 @@ import zipfile
from django.contrib import messages from django.contrib import messages
from django.contrib.admin import helpers from django.contrib.admin import helpers
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseServerError from django.http import HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
from orchestra.admin.forms import adminmodelformset_factory from orchestra.admin.forms import adminmodelformset_factory
from orchestra.admin.utils import get_object_from_url from orchestra.admin.utils import get_object_from_url, change_url
from orchestra.utils.html import html_to_pdf from orchestra.utils.html import html_to_pdf
from .forms import SelectSourceForm from .forms import SelectSourceForm
from .helpers import validate_contact
def validate_contact(bill):
""" checks if all the preconditions for bill generation are met """
msg = ''
if not hasattr(bill.account, 'invoicecontact'):
account = force_text(bill.account)
link = reverse('admin:accounts_account_change', args=(bill.account_id,))
link += '#invoicecontact-group'
msg += _('Related account "%s" doesn\'t have a declared invoice contact\n') % account
msg += _('You should <a href="%s">provide</a> one') % link
main = type(bill).account.field.rel.to.get_main()
if not hasattr(main, 'invoicecontact'):
account = force_text(main)
link = reverse('admin:accounts_account_change', args=(main.id,))
link += '#invoicecontact-group'
msg += _('Main account "%s" doesn\'t have a declared invoice contact\n') % account
msg += _('You should <a href="%s">provide</a> one') % link
if msg:
# TODO custom template
return HttpResponseServerError(mark_safe(msg))
def download_bills(modeladmin, request, queryset): def download_bills(modeladmin, request, queryset):
@ -42,15 +22,14 @@ def download_bills(modeladmin, request, queryset):
stringio = StringIO.StringIO() stringio = StringIO.StringIO()
archive = zipfile.ZipFile(stringio, 'w') archive = zipfile.ZipFile(stringio, 'w')
for bill in queryset: for bill in queryset:
html = bill.html or bill.render() pdf = html_to_pdf(bill.html or bill.render())
pdf = html_to_pdf(html)
archive.writestr('%s.pdf' % bill.number, pdf) archive.writestr('%s.pdf' % bill.number, pdf)
archive.close() archive.close()
response = HttpResponse(stringio.getvalue(), content_type='application/pdf') response = HttpResponse(stringio.getvalue(), content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"' response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
return response return response
bill = queryset.get() bill = queryset.get()
pdf = html_to_pdf(bill.html) pdf = html_to_pdf(bill.html or bill.render())
return HttpResponse(pdf, content_type='application/pdf') return HttpResponse(pdf, content_type='application/pdf')
download_bills.verbose_name = _("Download") download_bills.verbose_name = _("Download")
download_bills.url_name = 'download' download_bills.url_name = 'download'
@ -58,9 +37,8 @@ download_bills.url_name = 'download'
def view_bill(modeladmin, request, queryset): def view_bill(modeladmin, request, queryset):
bill = queryset.get() bill = queryset.get()
error = validate_contact(bill) if not validate_contact(request, bill):
if error: return
return error
html = bill.html or bill.render() html = bill.html or bill.render()
return HttpResponse(html) return HttpResponse(html)
view_bill.verbose_name = _("View") view_bill.verbose_name = _("View")
@ -73,20 +51,34 @@ def close_bills(modeladmin, request, queryset):
messages.warning(request, _("Selected bills should be in open state")) messages.warning(request, _("Selected bills should be in open state"))
return return
for bill in queryset: for bill in queryset:
error = validate_contact(bill) if not validate_contact(request, bill):
if error: return
return error
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0) SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
formset = SelectSourceFormSet(queryset=queryset) formset = SelectSourceFormSet(queryset=queryset)
if request.POST.get('post') == 'generic_confirmation': if request.POST.get('post') == 'generic_confirmation':
formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset) formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset)
if formset.is_valid(): if formset.is_valid():
transactions = []
for form in formset.forms: for form in formset.forms:
source = form.cleaned_data['source'] source = form.cleaned_data['source']
form.instance.close(payment=source) transaction = form.instance.close(payment=source)
if transaction:
transactions.append(transaction)
for bill in queryset: for bill in queryset:
modeladmin.log_change(request, bill, 'Closed') modeladmin.log_change(request, bill, 'Closed')
messages.success(request, _("Selected bills have been closed")) messages.success(request, _("Selected bills have been closed"))
if transactions:
num = len(transactions)
if num == 1:
url = change_url(transactions[0])
else:
url = reverse('admin:transactions_transaction_changelist')
url += 'id__in=%s' % ','.join([str(t.id) for t in transactions])
message = ungettext(
_('<a href="%s">One related transaction</a> has been created') % url,
_('<a href="%s">%i related transactions</a> have been created') % (url, num),
num)
messages.success(request, mark_safe(message))
return return
opts = modeladmin.model._meta opts = modeladmin.model._meta
context = { context = {
@ -110,11 +102,10 @@ close_bills.url_name = 'close'
def send_bills(modeladmin, request, queryset): def send_bills(modeladmin, request, queryset):
for bill in queryset: for bill in queryset:
error = validate_contact(bill) if not validate_contact(request, bill):
if error: return
return error
for bill in queryset: for bill in queryset:
bill.send() bill.send()
modeladmin.log_change(request, bill, 'Sent') modeladmin.log_change(request, bill, 'Sent')
send_bills.verbose_name = _("Send") send_bills.verbose_name = lambda bill: _("Resend" if getattr(bill, 'is_sent', False) else "Send")
send_bills.url_name = 'send' send_bills.url_name = 'send'

View File

@ -1,5 +1,5 @@
from django import forms from django import forms
from django.contrib import admin, messages from django.contrib import admin
from django.contrib.admin.utils import unquote 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
@ -11,7 +11,7 @@ from orchestra.admin.utils import 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 download_bills, view_bill, close_bills, send_bills from .actions import download_bills, view_bill, close_bills, send_bills, validate_contact
from .filters import BillTypeListFilter from .filters import BillTypeListFilter
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine
@ -55,9 +55,9 @@ class BillLineInline(admin.TabularInline):
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = ( list_display = (
'number', 'type_link', 'account_link', 'created_on_display', 'number', 'type_link', 'account_link', 'created_on_display',
'num_lines', 'display_total', 'display_payment_state', 'is_open' 'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent'
) )
list_filter = (BillTypeListFilter, 'is_open',) list_filter = (BillTypeListFilter, 'is_open', 'is_sent')
add_fields = ('account', 'type', 'is_open', 'due_on', 'comments') add_fields = ('account', 'type', 'is_open', 'due_on', 'comments')
fieldsets = ( fieldsets = (
(None, { (None, {
@ -97,9 +97,14 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
type_link.admin_order_field = 'type' type_link.admin_order_field = 'type'
def display_payment_state(self, bill): def display_payment_state(self, bill):
topts = bill.transactions.model._meta t_opts = bill.transactions.model._meta
url = reverse('admin:%s_%s_changelist' % (topts.app_label, topts.module_name)) transactions = bill.transactions.all()
url += '?bill=%i' % bill.pk if len(transactions) == 1:
args = (transactions[0].pk,)
url = reverse('admin:%s_%s_change' % (t_opts.app_label, t_opts.module_name), args=args)
else:
url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.module_name))
url += '?bill=%i' % bill.pk
state = bill.get_payment_state_display().upper() state = bill.get_payment_state_display().upper()
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey') color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}">{name}</a>'.format( return '<a href="{url}" style="color:{color}">{name}</a>'.format(
@ -120,7 +125,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
return fieldsets return fieldsets
def get_change_view_actions(self, obj=None): def get_change_view_actions(self, obj=None):
actions = super(BillAdmin, self).get_change_view_actions() actions = super(BillAdmin, self).get_change_view_actions(obj=obj)
exclude = [] exclude = []
if obj: if obj:
if not obj.is_open: if not obj.is_open:
@ -143,19 +148,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
qs = super(BillAdmin, self).get_queryset(request) qs = super(BillAdmin, self).get_queryset(request)
qs = qs.annotate(models.Count('lines')) qs = qs.annotate(models.Count('lines'))
qs = qs.prefetch_related('lines', 'lines__sublines') qs = qs.prefetch_related('lines', 'lines__sublines', 'transactions')
return qs return qs
def change_view(self, request, object_id, **kwargs): def change_view(self, request, object_id, **kwargs):
bill = self.get_object(request, unquote(object_id))
# TODO raise404, here and everywhere # TODO raise404, here and everywhere
if not hasattr(bill.account, 'invoicecontact'): bill = self.get_object(request, unquote(object_id))
create_link = reverse('admin:accounts_account_change', args=(bill.account_id,)) validate_contact(request, bill, error=False)
create_link += '#invoicecontact-group'
messages.warning(request, mark_safe(_(
'Be aware, related contact doesn\'t have a billing contact defined, '
'bill can not be generated until one is <a href="%s">provided</a>' % create_link
)))
return super(BillAdmin, self).change_view(request, object_id, **kwargs) return super(BillAdmin, self).change_view(request, object_id, **kwargs)

View File

@ -0,0 +1,28 @@
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
def validate_contact(request, bill, error=True):
""" checks if all the preconditions for bill generation are met """
msg = _('{relation} account "{account}" does not have a declared invoice contact. '
'You should <a href="{url}#invoicecontact-group">provide one</a>')
valid = True
send = messages.error if error else messages.warning
if not hasattr(bill.account, 'invoicecontact'):
account = force_text(bill.account)
url = reverse('admin:accounts_account_change', args=(bill.account_id,))
message = msg.format(relation=_("Related"), account=account, url=url)
send(request, mark_safe(message))
valid = False
main = type(bill).account.field.rel.to.get_main()
if not hasattr(main, 'invoicecontact'):
account = force_text(main)
url = reverse('admin:accounts_account_change', args=(main.id,))
message = msg.format(relation=_("Main"), account=account, url=url)
send(request, mark_safe(message))
valid = False
return valid

View File

@ -81,7 +81,7 @@ class Bill(models.Model):
@cached_property @cached_property
def payment_state(self): def payment_state(self):
if self.is_open: if self.is_open or self.get_type() == self.PROFORMA:
return self.OPEN return self.OPEN
secured = self.transactions.secured().amount() secured = self.transactions.secured().amount()
if secured >= self.total: if secured >= self.total:
@ -136,12 +136,14 @@ class Bill(models.Model):
self.due_on = self.get_due_date(payment=payment) self.due_on = self.get_due_date(payment=payment)
self.total = self.get_total() self.total = self.get_total()
self.html = self.render(payment=payment) self.html = self.render(payment=payment)
transaction = None
if self.get_type() != self.PROFORMA: if self.get_type() != self.PROFORMA:
self.transactions.create(bill=self, source=payment, amount=self.total) transaction = self.transactions.create(bill=self, source=payment, amount=self.total)
self.closed_on = timezone.now() self.closed_on = timezone.now()
self.is_open = False self.is_open = False
self.is_sent = False self.is_sent = False
self.save() self.save()
return transaction
def send(self): def send(self):
html = self.html or self.render() html = self.html or self.render()

View File

@ -1,2 +1,6 @@
{% if subject %}Bill {{ bill.number }}{% endif %} {% if subject %}Bill {{ bill.number }}{% endif %}
{% if message %}Find attached your invoice{% endif %} {% if message %}Dear {{ bill.account.username }},
Find your {{ bill.get_type.lower }} attached.
If you have any question, please write us at support@orchestra.lan
{% endif %}

View File

@ -1,6 +1,7 @@
from functools import partial from functools import partial
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins from orchestra.utils import plugins
@ -111,6 +112,11 @@ class ServiceBackend(plugins.Plugin):
class ServiceController(ServiceBackend): class ServiceController(ServiceBackend):
actions = ('save', 'delete') actions = ('save', 'delete')
abstract = True
@classmethod
def get_verbose_name(cls):
return _("[S] %s") % super(ServiceController, cls).get_verbose_name()
@classmethod @classmethod
def get_backends(cls): def get_backends(cls):

View File

@ -2,10 +2,11 @@ from django.contrib import admin, messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
from django.utils.translation import ungettext
from django.shortcuts import render from django.shortcuts import render
from orchestra.admin.utils import change_url
from .forms import (BillSelectedOptionsForm, BillSelectConfirmationForm, from .forms import (BillSelectedOptionsForm, BillSelectConfirmationForm,
BillSelectRelatedForm) BillSelectRelatedForm)
@ -38,7 +39,7 @@ class BillSelectedOrders(object):
self.options = dict( self.options = dict(
billing_point=form.cleaned_data['billing_point'], billing_point=form.cleaned_data['billing_point'],
fixed_point=form.cleaned_data['fixed_point'], fixed_point=form.cleaned_data['fixed_point'],
is_proforma=form.cleaned_data['is_proforma'], proforma=form.cleaned_data['proforma'],
new_open=form.cleaned_data['new_open'], new_open=form.cleaned_data['new_open'],
) )
if int(request.POST.get('step')) != 3: if int(request.POST.get('step')) != 3:
@ -78,14 +79,18 @@ class BillSelectedOrders(object):
if int(request.POST.get('step')) >= 3: if int(request.POST.get('step')) >= 3:
bills = self.queryset.bill(commit=True, **self.options) bills = self.queryset.bill(commit=True, **self.options)
for order in self.queryset: for order in self.queryset:
self.modeladmin.log_change(request, order, 'Billed') self.modeladmin.log_change(request, order, _("Billed"))
if not bills: if not bills:
msg = _("Selected orders do not have pending billing") msg = _("Selected orders do not have pending billing")
self.modeladmin.message_user(request, msg, messages.WARNING) self.modeladmin.message_user(request, msg, messages.WARNING)
else: else:
ids = ','.join([str(bill.id) for bill in bills]) num = len(bills)
url = reverse('admin:bills_bill_changelist') if num == 1:
url += '?id__in=%s' % ids url = change_url(bills[0])
else:
url = reverse('admin:bills_bill_changelist')
ids = ','.join([str(bill.id) for bill in bills])
url += '?id__in=%s' % ids
num = len(bills) num = len(bills)
msg = ungettext( msg = ungettext(
'<a href="{url}">One bill</a> has been created.', '<a href="{url}">One bill</a> has been created.',

View File

@ -18,7 +18,7 @@ class BillSelectedOptionsForm(AdminFormMixin, forms.Form):
label=_("Fixed point"), label=_("Fixed point"),
help_text=_("Deisgnates whether you want the billing point to be an " help_text=_("Deisgnates whether you want the billing point to be an "
"exact date, or adapt it to the billing period.")) "exact date, or adapt it to the billing period."))
is_proforma = forms.BooleanField(initial=False, required=False, proforma = forms.BooleanField(initial=False, required=False,
label=_("Pro-forma (billing simulation)"), label=_("Pro-forma (billing simulation)"),
help_text=_("Creates a Pro Forma instead of billing the orders.")) help_text=_("Creates a Pro Forma instead of billing the orders."))
new_open = forms.BooleanField(initial=False, required=False, new_open = forms.BooleanField(initial=False, required=False,
@ -49,7 +49,7 @@ class BillSelectRelatedForm(AdminFormMixin, forms.Form):
required=False) required=False)
billing_point = forms.DateField(widget=forms.HiddenInput()) billing_point = forms.DateField(widget=forms.HiddenInput())
fixed_point = forms.BooleanField(widget=forms.HiddenInput(), required=False) fixed_point = forms.BooleanField(widget=forms.HiddenInput(), required=False)
is_proforma = forms.BooleanField(widget=forms.HiddenInput(), required=False) proforma = forms.BooleanField(widget=forms.HiddenInput(), required=False)
create_new_open = forms.BooleanField(widget=forms.HiddenInput(), required=False) create_new_open = forms.BooleanField(widget=forms.HiddenInput(), required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -63,5 +63,5 @@ class BillSelectRelatedForm(AdminFormMixin, forms.Form):
class BillSelectConfirmationForm(AdminFormMixin, forms.Form): class BillSelectConfirmationForm(AdminFormMixin, forms.Form):
billing_point = forms.DateField(widget=forms.HiddenInput()) billing_point = forms.DateField(widget=forms.HiddenInput())
fixed_point = forms.BooleanField(widget=forms.HiddenInput(), required=False) fixed_point = forms.BooleanField(widget=forms.HiddenInput(), required=False)
is_proforma = forms.BooleanField(widget=forms.HiddenInput(), required=False) proforma = forms.BooleanField(widget=forms.HiddenInput(), required=False)
create_new_open = forms.BooleanField(widget=forms.HiddenInput(), required=False) create_new_open = forms.BooleanField(widget=forms.HiddenInput(), required=False)

View File

@ -92,7 +92,7 @@ class TransactionAdmin(ChangeViewActionsMixin, AccountAdminMixin, admin.ModelAdm
class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin): class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
list_display = ('id', 'file_url', 'display_transactions', 'created_at') list_display = ('id', 'file_url', 'display_transactions', 'created_at')
fields = ('data', 'file_url', 'display_transactions', 'created_at') fields = ('data', 'file_url', 'created_at')
readonly_fields = ('file_url', 'display_transactions', 'created_at') readonly_fields = ('file_url', 'display_transactions', 'created_at')
inlines = [TransactionInline] inlines = [TransactionInline]
actions = (actions.mark_process_as_executed, actions.abort, actions.commit) actions = (actions.mark_process_as_executed, actions.abort, actions.commit)
@ -111,16 +111,17 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
# Because of values_list this query doesn't benefit from prefetch_related # Because of values_list this query doesn't benefit from prefetch_related
tx_ids = process.transactions.values_list('id', flat=True) tx_ids = process.transactions.values_list('id', flat=True)
for tx_id in tx_ids: for tx_id in tx_ids:
ids.append(str(tx_id)) ids.append('#%i' % tx_id)
counter += 1 counter += 1
if counter > 10: if counter > 10:
counter = 0 counter = 0
lines.append(','.join(ids)) lines.append(','.join(ids))
ids = [] ids = []
lines.append(','.join(ids)) lines.append(','.join(ids))
transactions = '<br'.join(lines)
url = reverse('admin:payments_transaction_changelist') url = reverse('admin:payments_transaction_changelist')
url += '?processes=%i' % process.id url += '?processes=%i' % process.id
return '<a href="%s">%s</a>' % (url, '<br>'.join(lines)) return '<a href="%s">%s</a>' % (url, transactions)
display_transactions.short_description = _("Transactions") display_transactions.short_description = _("Transactions")
display_transactions.allow_tags = True display_transactions.allow_tags = True

View File

@ -175,10 +175,10 @@ class SEPADirectDebit(PaymentMethod):
def get_debt_transactions(cls, transactions, process): def get_debt_transactions(cls, transactions, process):
for transaction in transactions: for transaction in transactions:
transaction.process = process transaction.process = process
transaction.state = transaction.WAITTING_EXECUTION
transaction.save(update_fields=['state', 'process'])
account = transaction.account account = transaction.account
data = transaction.source.data data = transaction.source.data
transaction.state = transaction.WAITTING_CONFIRMATION
transaction.save(update_fields=['state'])
yield E.DrctDbtTxInf( # Direct Debit Transaction Info yield E.DrctDbtTxInf( # Direct Debit Transaction Info
E.PmtId( # Payment Id E.PmtId( # Payment Id
E.EndToEndId(str(transaction.id)) # Payment Id/End to End E.EndToEndId(str(transaction.id)) # Payment Id/End to End
@ -191,7 +191,7 @@ class SEPADirectDebit(PaymentMethod):
E.MndtRltdInf( # Mandate Related Info E.MndtRltdInf( # Mandate Related Info
E.MndtId(str(account.id)), # Mandate Id E.MndtId(str(account.id)), # Mandate Id
E.DtOfSgntr( # Date of Signature E.DtOfSgntr( # Date of Signature
account.register_date.strftime("%Y-%m-%d") account.date_joined.strftime("%Y-%m-%d")
) )
) )
), ),
@ -216,10 +216,10 @@ class SEPADirectDebit(PaymentMethod):
def get_credit_transactions(transactions, process): def get_credit_transactions(transactions, process):
for transaction in transactions: for transaction in transactions:
transaction.process = process transaction.process = process
transaction.state = transaction.WAITTING_EXECUTION
transaction.save(update_fields=['state', 'process'])
account = transaction.account account = transaction.account
data = transaction.source.data data = transaction.source.data
transaction.state = transaction.WAITTING_CONFIRMATION
transaction.save(update_fields=['state'])
yield E.CdtTrfTxInf( # Credit Transfer Transaction Info yield E.CdtTrfTxInf( # Credit Transfer Transaction Info
E.PmtId( # Payment Id E.PmtId( # Payment Id
E.EndToEndId(str(transaction.id)) # Payment Id/End to End E.EndToEndId(str(transaction.id)) # Payment Id/End to End

View File

@ -104,7 +104,7 @@ class Transaction(models.Model):
objects = TransactionQuerySet.as_manager() objects = TransactionQuerySet.as_manager()
def __unicode__(self): def __unicode__(self):
return "Transaction {}".format(self.id) return "Transaction #{}".format(self.id)
@property @property
def account(self): def account(self):
@ -162,7 +162,7 @@ class TransactionProcess(models.Model):
verbose_name_plural = _("Transaction processes") verbose_name_plural = _("Transaction processes")
def __unicode__(self): def __unicode__(self):
return str(self.id) return '#%i' % self.id
def mark_as_executed(self): def mark_as_executed(self):
assert self.state == self.CREATED assert self.state == self.CREATED

View File

@ -3,6 +3,7 @@ import datetime
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceBackend from orchestra.apps.orchestration import ServiceBackend
@ -13,15 +14,19 @@ class ServiceMonitor(ServiceBackend):
MEMORY = 'memory' MEMORY = 'memory'
CPU = 'cpu' CPU = 'cpu'
# TODO UNITS # TODO UNITS
actions = ('monitor', 'exceeded', 'recovery') actions = ('monitor', 'exceeded', 'recovery')
abstract = True
@classmethod @classmethod
def get_backends(cls): def get_backends(cls):
""" filter monitor classes """ """ filter controller classes """
for backend in cls.plugins: return [
if backend != ServiceMonitor and ServiceMonitor in backend.__mro__: plugin for plugin in cls.plugins if ServiceMonitor in plugin.__mro__
yield backend ]
@classmethod
def get_verbose_name(cls):
return _("[M] %s") % super(ServiceMonitor, cls).get_verbose_name()
@cached_property @cached_property
def current_date(self): def current_date(self):

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm from orchestra.forms import PluginDataForm
@ -6,16 +7,24 @@ from orchestra.forms import PluginDataForm
from .options import SoftwareService from .options import SoftwareService
class WordpressForm(PluginDataForm): class WordPressForm(PluginDataForm):
username = forms.CharField(label=_("Username"), max_length=64) username = forms.CharField(label=_("Username"), max_length=64)
password = forms.CharField(label=_("Password"), max_length=64) password = forms.CharField(label=_("Password"), max_length=64)
site_name = forms.CharField(label=_("Site name"), max_length=64, site_name = forms.CharField(label=_("Site name"), max_length=64,
help_text=_("URL will be &lt;site_name&gt;.blogs.orchestra.lan")) help_text=_("URL will be &lt;site_name&gt;.blogs.orchestra.lan"))
email = forms.EmailField(label=_("Email")) email = forms.EmailField(label=_("Email"))
def __init__(self, *args, **kwargs):
super(WordPressForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
if instance:
url = 'http://%s.%s' % (instance.data['site_name'], 'blogs.orchestra.lan')
url = '<a href="%s">%s</a>' % (url, url)
self.fields['site_name'].help_text = mark_safe(url)
class WordpressService(SoftwareService): class WordpressService(SoftwareService):
verbose_name = "WordPress" verbose_name = "WordPress"
form = WordpressForm form = WordPressForm
description_field = 'site_name' description_field = 'site_name'
icon = 'saas/icons/WordPress.png' icon = 'saas/icons/WordPress.png'

View File

@ -28,7 +28,7 @@
{% else %} {% else %}
<ul> <ul>
{% for plugin in plugins %} {% for plugin in plugins %}
<li><a style="font-size:small;" href="../?{{ field }}={{ plugin.get_name }}&{{ request.META.QUERY_STRING }}">{{ plugin.verbose_name }}</<a></li> <li><a style="font-size:small;" href="../?{{ field }}={{ plugin.get_plugin_name }}&{{ request.META.QUERY_STRING }}">{{ plugin.verbose_name }}</<a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View File

@ -23,31 +23,35 @@ class Plugin(object):
return plugin return plugin
raise KeyError('This plugin is not registered') raise KeyError('This plugin is not registered')
@classmethod
def get_verbose_name(cls):
# don't evaluate p.verbose_name ugettext_lazy
verbose = getattr(cls.verbose_name, '_proxy____args', [cls.verbose_name])
if verbose[0]:
return cls.verbose_name
else:
return cls.get_plugin_name()
@classmethod @classmethod
def get_plugin_choices(cls): def get_plugin_choices(cls):
plugins = cls.get_plugins()
choices = [] choices = []
for p in plugins: for plugin in cls.get_plugins():
# don't evaluate p.verbose_name ugettext_lazy verbose = plugin.get_verbose_name()
verbose = getattr(p.verbose_name, '_proxy____args', [p.verbose_name]) choices.append((plugin.get_plugin_name(), verbose))
if verbose[0]: return sorted(choices, key=lambda e: e[1])
verbose = p.verbose_name
else:
verbose = p.get_plugin_name()
choices.append((p.get_plugin_name(), verbose))
return sorted(choices, key=lambda e: e[0])
class PluginMount(type): class PluginMount(type):
def __init__(cls, name, bases, attrs): def __init__(cls, name, bases, attrs):
if not hasattr(cls, 'plugins'): if not attrs.get('abstract', False):
# This branch only executes when processing the mount point itself. if not hasattr(cls, 'plugins'):
# So, since this is a new plugin type, not an implementation, this # This branch only executes when processing the mount point itself.
# class shouldn't be registered as a plugin. Instead, it sets up a # So, since this is a new plugin type, not an implementation, this
# list where plugins can be registered later. # class shouldn't be registered as a plugin. Instead, it sets up a
cls.plugins = [] # list where plugins can be registered later.
else: cls.plugins = []
# This must be a plugin implementation, which should be registered. else:
# Simply appending it to the list is all that's needed to keep # This must be a plugin implementation, which should be registered.
# track of it later. # Simply appending it to the list is all that's needed to keep
cls.plugins.append(cls) # track of it later.
cls.plugins.append(cls)

View File

@ -0,0 +1,48 @@
apt-get install mailman
newlist mailman
echo 'mailman: "|/var/lib/mailman/mail/mailman post mailman"
mailman-admin: "|/var/lib/mailman/mail/mailman admin mailman"
mailman-bounces: "|/var/lib/mailman/mail/mailman bounces mailman"
mailman-confirm: "|/var/lib/mailman/mail/mailman confirm mailman"
mailman-join: "|/var/lib/mailman/mail/mailman join mailman"
mailman-leave: "|/var/lib/mailman/mail/mailman leave mailman"
mailman-owner: "|/var/lib/mailman/mail/mailman owner mailman"
mailman-request: "|/var/lib/mailman/mail/mailman request mailman"
mailman-subscribe: "|/var/lib/mailman/mail/mailman subscribe mailman"
mailman-unsubscribe: "|/var/lib/mailman/mail/mailman unsubscribe mailman"' >> /etc/aliases
postalias /etc/aliases
/etc/init.d/mailman start
# postifx
relay_domains = $mydestination, lists.orchestra.lan
relay_recipient_maps = hash:/var/lib/mailman/data/aliases
transport_maps = hash:/etc/postfix/transport
mailman_destination_recipient_limit = 1
echo "lists.orchestra.lan mailman:" >> /etc/postfix/transport
postmap /etc/postfix/transport
echo 'MTA = "Postfix"
POSTFIX_STYLE_VIRTUAL_DOMAINS = ["lists.orchestra.lan"]
# alias for postmaster, abuse and mailer-daemon
DEB_LISTMASTER = "postmaster@orchestra.lan"' >> /etc/mailman/mm_cfg.py
sed -i "s/DEFAULT_EMAIL_HOST\s*=\s*.*/DEFAULT_EMAIL_HOST = 'lists.orchestra.lan'/" /etc/mailman/mm_cfg.py
sed -i "s/DEFAULT_URL_HOST\s*=\s*.*/DEFAULT_URL_HOST = 'lists.orchestra.lan'/" /etc/mailman/mm_cfg.py