Random fixes
This commit is contained in:
parent
1c8ef622b0
commit
920f8efcd5
4
TODO.md
4
TODO.md
|
@ -90,7 +90,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
|||
|
||||
* 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?
|
||||
|
||||
|
@ -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?
|
||||
|
||||
* 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?
|
||||
|
||||
* Redirect junk emails and delete every 30 days?
|
||||
|
|
|
@ -86,8 +86,11 @@ class ChangeViewActionsMixin(object):
|
|||
action = getattr(self, action)
|
||||
view = action_to_view(action, self)
|
||||
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('_', ' '))
|
||||
if hasattr(verbose_name, '__call__'):
|
||||
verbose_name = verbose_name(obj)
|
||||
view.verbose_name = verbose_name
|
||||
view.css_class = getattr(action, 'css_class', 'historylink')
|
||||
view.description = getattr(action, 'description', '')
|
||||
views.append(view)
|
||||
|
@ -186,7 +189,7 @@ class SelectPluginAdminMixin(object):
|
|||
def add_view(self, request, form_url='', extra_context=None):
|
||||
""" Redirects to select account view if required """
|
||||
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:
|
||||
self.plugin_value = plugin_value
|
||||
if not plugin_value:
|
||||
|
|
|
@ -4,37 +4,17 @@ import zipfile
|
|||
from django.contrib import messages
|
||||
from django.contrib.admin import helpers
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseServerError
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.encoding import force_text
|
||||
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.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 .forms import SelectSourceForm
|
||||
|
||||
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))
|
||||
from .helpers import validate_contact
|
||||
|
||||
|
||||
def download_bills(modeladmin, request, queryset):
|
||||
|
@ -42,15 +22,14 @@ def download_bills(modeladmin, request, queryset):
|
|||
stringio = StringIO.StringIO()
|
||||
archive = zipfile.ZipFile(stringio, 'w')
|
||||
for bill in queryset:
|
||||
html = bill.html or bill.render()
|
||||
pdf = html_to_pdf(html)
|
||||
pdf = html_to_pdf(bill.html or bill.render())
|
||||
archive.writestr('%s.pdf' % bill.number, pdf)
|
||||
archive.close()
|
||||
response = HttpResponse(stringio.getvalue(), content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
|
||||
return response
|
||||
bill = queryset.get()
|
||||
pdf = html_to_pdf(bill.html)
|
||||
pdf = html_to_pdf(bill.html or bill.render())
|
||||
return HttpResponse(pdf, content_type='application/pdf')
|
||||
download_bills.verbose_name = _("Download")
|
||||
download_bills.url_name = 'download'
|
||||
|
@ -58,9 +37,8 @@ download_bills.url_name = 'download'
|
|||
|
||||
def view_bill(modeladmin, request, queryset):
|
||||
bill = queryset.get()
|
||||
error = validate_contact(bill)
|
||||
if error:
|
||||
return error
|
||||
if not validate_contact(request, bill):
|
||||
return
|
||||
html = bill.html or bill.render()
|
||||
return HttpResponse(html)
|
||||
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"))
|
||||
return
|
||||
for bill in queryset:
|
||||
error = validate_contact(bill)
|
||||
if error:
|
||||
return error
|
||||
if not validate_contact(request, bill):
|
||||
return
|
||||
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
|
||||
formset = SelectSourceFormSet(queryset=queryset)
|
||||
if request.POST.get('post') == 'generic_confirmation':
|
||||
formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset)
|
||||
if formset.is_valid():
|
||||
transactions = []
|
||||
for form in formset.forms:
|
||||
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:
|
||||
modeladmin.log_change(request, bill, '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
|
||||
opts = modeladmin.model._meta
|
||||
context = {
|
||||
|
@ -110,11 +102,10 @@ close_bills.url_name = 'close'
|
|||
|
||||
def send_bills(modeladmin, request, queryset):
|
||||
for bill in queryset:
|
||||
error = validate_contact(bill)
|
||||
if error:
|
||||
return error
|
||||
if not validate_contact(request, bill):
|
||||
return
|
||||
for bill in queryset:
|
||||
bill.send()
|
||||
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'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django import forms
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.utils import unquote
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
|
@ -11,7 +11,7 @@ from orchestra.admin.utils import admin_date
|
|||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||
|
||||
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 .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine
|
||||
|
||||
|
@ -55,9 +55,9 @@ class BillLineInline(admin.TabularInline):
|
|||
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||
list_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')
|
||||
fieldsets = (
|
||||
(None, {
|
||||
|
@ -97,8 +97,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
type_link.admin_order_field = 'type'
|
||||
|
||||
def display_payment_state(self, bill):
|
||||
topts = bill.transactions.model._meta
|
||||
url = reverse('admin:%s_%s_changelist' % (topts.app_label, topts.module_name))
|
||||
t_opts = bill.transactions.model._meta
|
||||
transactions = bill.transactions.all()
|
||||
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()
|
||||
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
|
||||
|
@ -120,7 +125,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
return fieldsets
|
||||
|
||||
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 = []
|
||||
if obj:
|
||||
if not obj.is_open:
|
||||
|
@ -143,19 +148,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
def get_queryset(self, request):
|
||||
qs = super(BillAdmin, self).get_queryset(request)
|
||||
qs = qs.annotate(models.Count('lines'))
|
||||
qs = qs.prefetch_related('lines', 'lines__sublines')
|
||||
qs = qs.prefetch_related('lines', 'lines__sublines', 'transactions')
|
||||
return qs
|
||||
|
||||
def change_view(self, request, object_id, **kwargs):
|
||||
bill = self.get_object(request, unquote(object_id))
|
||||
# TODO raise404, here and everywhere
|
||||
if not hasattr(bill.account, 'invoicecontact'):
|
||||
create_link = reverse('admin:accounts_account_change', args=(bill.account_id,))
|
||||
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
|
||||
)))
|
||||
bill = self.get_object(request, unquote(object_id))
|
||||
validate_contact(request, bill, error=False)
|
||||
return super(BillAdmin, self).change_view(request, object_id, **kwargs)
|
||||
|
||||
|
||||
|
|
28
orchestra/apps/bills/helpers.py
Normal file
28
orchestra/apps/bills/helpers.py
Normal 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
|
||||
|
|
@ -81,7 +81,7 @@ class Bill(models.Model):
|
|||
|
||||
@cached_property
|
||||
def payment_state(self):
|
||||
if self.is_open:
|
||||
if self.is_open or self.get_type() == self.PROFORMA:
|
||||
return self.OPEN
|
||||
secured = self.transactions.secured().amount()
|
||||
if secured >= self.total:
|
||||
|
@ -136,12 +136,14 @@ class Bill(models.Model):
|
|||
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:
|
||||
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.is_open = False
|
||||
self.is_sent = False
|
||||
self.save()
|
||||
return transaction
|
||||
|
||||
def send(self):
|
||||
html = self.html or self.render()
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
{% 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 %}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from functools import partial
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.utils import plugins
|
||||
|
||||
|
@ -111,6 +112,11 @@ class ServiceBackend(plugins.Plugin):
|
|||
|
||||
class ServiceController(ServiceBackend):
|
||||
actions = ('save', 'delete')
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def get_verbose_name(cls):
|
||||
return _("[S] %s") % super(ServiceController, cls).get_verbose_name()
|
||||
|
||||
@classmethod
|
||||
def get_backends(cls):
|
||||
|
|
|
@ -2,10 +2,11 @@ from django.contrib import admin, messages
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext
|
||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||
from django.shortcuts import render
|
||||
|
||||
from orchestra.admin.utils import change_url
|
||||
|
||||
from .forms import (BillSelectedOptionsForm, BillSelectConfirmationForm,
|
||||
BillSelectRelatedForm)
|
||||
|
||||
|
@ -38,7 +39,7 @@ class BillSelectedOrders(object):
|
|||
self.options = dict(
|
||||
billing_point=form.cleaned_data['billing_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'],
|
||||
)
|
||||
if int(request.POST.get('step')) != 3:
|
||||
|
@ -78,13 +79,17 @@ class BillSelectedOrders(object):
|
|||
if int(request.POST.get('step')) >= 3:
|
||||
bills = self.queryset.bill(commit=True, **self.options)
|
||||
for order in self.queryset:
|
||||
self.modeladmin.log_change(request, order, 'Billed')
|
||||
self.modeladmin.log_change(request, order, _("Billed"))
|
||||
if not bills:
|
||||
msg = _("Selected orders do not have pending billing")
|
||||
self.modeladmin.message_user(request, msg, messages.WARNING)
|
||||
else:
|
||||
ids = ','.join([str(bill.id) for bill in bills])
|
||||
num = len(bills)
|
||||
if num == 1:
|
||||
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)
|
||||
msg = ungettext(
|
||||
|
|
|
@ -18,7 +18,7 @@ class BillSelectedOptionsForm(AdminFormMixin, forms.Form):
|
|||
label=_("Fixed point"),
|
||||
help_text=_("Deisgnates whether you want the billing point to be an "
|
||||
"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)"),
|
||||
help_text=_("Creates a Pro Forma instead of billing the orders."))
|
||||
new_open = forms.BooleanField(initial=False, required=False,
|
||||
|
@ -49,7 +49,7 @@ class BillSelectRelatedForm(AdminFormMixin, forms.Form):
|
|||
required=False)
|
||||
billing_point = forms.DateField(widget=forms.HiddenInput())
|
||||
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)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -63,5 +63,5 @@ class BillSelectRelatedForm(AdminFormMixin, forms.Form):
|
|||
class BillSelectConfirmationForm(AdminFormMixin, forms.Form):
|
||||
billing_point = forms.DateField(widget=forms.HiddenInput())
|
||||
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)
|
||||
|
|
|
@ -92,7 +92,7 @@ class TransactionAdmin(ChangeViewActionsMixin, AccountAdminMixin, admin.ModelAdm
|
|||
|
||||
class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||
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')
|
||||
inlines = [TransactionInline]
|
||||
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
|
||||
tx_ids = process.transactions.values_list('id', flat=True)
|
||||
for tx_id in tx_ids:
|
||||
ids.append(str(tx_id))
|
||||
ids.append('#%i' % tx_id)
|
||||
counter += 1
|
||||
if counter > 10:
|
||||
counter = 0
|
||||
lines.append(','.join(ids))
|
||||
ids = []
|
||||
lines.append(','.join(ids))
|
||||
transactions = '<br'.join(lines)
|
||||
url = reverse('admin:payments_transaction_changelist')
|
||||
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.allow_tags = True
|
||||
|
||||
|
|
|
@ -175,10 +175,10 @@ class SEPADirectDebit(PaymentMethod):
|
|||
def get_debt_transactions(cls, transactions, process):
|
||||
for transaction in transactions:
|
||||
transaction.process = process
|
||||
transaction.state = transaction.WAITTING_EXECUTION
|
||||
transaction.save(update_fields=['state', 'process'])
|
||||
account = transaction.account
|
||||
data = transaction.source.data
|
||||
transaction.state = transaction.WAITTING_CONFIRMATION
|
||||
transaction.save(update_fields=['state'])
|
||||
yield E.DrctDbtTxInf( # Direct Debit Transaction Info
|
||||
E.PmtId( # Payment Id
|
||||
E.EndToEndId(str(transaction.id)) # Payment Id/End to End
|
||||
|
@ -191,7 +191,7 @@ class SEPADirectDebit(PaymentMethod):
|
|||
E.MndtRltdInf( # Mandate Related Info
|
||||
E.MndtId(str(account.id)), # Mandate Id
|
||||
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):
|
||||
for transaction in transactions:
|
||||
transaction.process = process
|
||||
transaction.state = transaction.WAITTING_EXECUTION
|
||||
transaction.save(update_fields=['state', 'process'])
|
||||
account = transaction.account
|
||||
data = transaction.source.data
|
||||
transaction.state = transaction.WAITTING_CONFIRMATION
|
||||
transaction.save(update_fields=['state'])
|
||||
yield E.CdtTrfTxInf( # Credit Transfer Transaction Info
|
||||
E.PmtId( # Payment Id
|
||||
E.EndToEndId(str(transaction.id)) # Payment Id/End to End
|
||||
|
|
|
@ -104,7 +104,7 @@ class Transaction(models.Model):
|
|||
objects = TransactionQuerySet.as_manager()
|
||||
|
||||
def __unicode__(self):
|
||||
return "Transaction {}".format(self.id)
|
||||
return "Transaction #{}".format(self.id)
|
||||
|
||||
@property
|
||||
def account(self):
|
||||
|
@ -162,7 +162,7 @@ class TransactionProcess(models.Model):
|
|||
verbose_name_plural = _("Transaction processes")
|
||||
|
||||
def __unicode__(self):
|
||||
return str(self.id)
|
||||
return '#%i' % self.id
|
||||
|
||||
def mark_as_executed(self):
|
||||
assert self.state == self.CREATED
|
||||
|
|
|
@ -3,6 +3,7 @@ import datetime
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.orchestration import ServiceBackend
|
||||
|
||||
|
@ -13,15 +14,19 @@ class ServiceMonitor(ServiceBackend):
|
|||
MEMORY = 'memory'
|
||||
CPU = 'cpu'
|
||||
# TODO UNITS
|
||||
|
||||
actions = ('monitor', 'exceeded', 'recovery')
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def get_backends(cls):
|
||||
""" filter monitor classes """
|
||||
for backend in cls.plugins:
|
||||
if backend != ServiceMonitor and ServiceMonitor in backend.__mro__:
|
||||
yield backend
|
||||
""" filter controller classes """
|
||||
return [
|
||||
plugin for plugin in cls.plugins if ServiceMonitor in plugin.__mro__
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_verbose_name(cls):
|
||||
return _("[M] %s") % super(ServiceMonitor, cls).get_verbose_name()
|
||||
|
||||
@cached_property
|
||||
def current_date(self):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django import forms
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.forms import PluginDataForm
|
||||
|
@ -6,16 +7,24 @@ from orchestra.forms import PluginDataForm
|
|||
from .options import SoftwareService
|
||||
|
||||
|
||||
class WordpressForm(PluginDataForm):
|
||||
class WordPressForm(PluginDataForm):
|
||||
username = forms.CharField(label=_("Username"), max_length=64)
|
||||
password = forms.CharField(label=_("Password"), max_length=64)
|
||||
site_name = forms.CharField(label=_("Site name"), max_length=64,
|
||||
help_text=_("URL will be <site_name>.blogs.orchestra.lan"))
|
||||
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):
|
||||
verbose_name = "WordPress"
|
||||
form = WordpressForm
|
||||
form = WordPressForm
|
||||
description_field = 'site_name'
|
||||
icon = 'saas/icons/WordPress.png'
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
{% else %}
|
||||
<ul>
|
||||
{% 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 %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
@ -24,22 +24,26 @@ class Plugin(object):
|
|||
raise KeyError('This plugin is not registered')
|
||||
|
||||
@classmethod
|
||||
def get_plugin_choices(cls):
|
||||
plugins = cls.get_plugins()
|
||||
choices = []
|
||||
for p in plugins:
|
||||
def get_verbose_name(cls):
|
||||
# don't evaluate p.verbose_name ugettext_lazy
|
||||
verbose = getattr(p.verbose_name, '_proxy____args', [p.verbose_name])
|
||||
verbose = getattr(cls.verbose_name, '_proxy____args', [cls.verbose_name])
|
||||
if verbose[0]:
|
||||
verbose = p.verbose_name
|
||||
return cls.verbose_name
|
||||
else:
|
||||
verbose = p.get_plugin_name()
|
||||
choices.append((p.get_plugin_name(), verbose))
|
||||
return sorted(choices, key=lambda e: e[0])
|
||||
return cls.get_plugin_name()
|
||||
|
||||
@classmethod
|
||||
def get_plugin_choices(cls):
|
||||
choices = []
|
||||
for plugin in cls.get_plugins():
|
||||
verbose = plugin.get_verbose_name()
|
||||
choices.append((plugin.get_plugin_name(), verbose))
|
||||
return sorted(choices, key=lambda e: e[1])
|
||||
|
||||
|
||||
class PluginMount(type):
|
||||
def __init__(cls, name, bases, attrs):
|
||||
if not attrs.get('abstract', False):
|
||||
if not hasattr(cls, 'plugins'):
|
||||
# This branch only executes when processing the mount point itself.
|
||||
# So, since this is a new plugin type, not an implementation, this
|
||||
|
|
48
scripts/services/mailman.md
Normal file
48
scripts/services/mailman.md
Normal 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
|
||||
|
||||
|
Loading…
Reference in a new issue