diff --git a/TODO.md b/TODO.md index d3210342..64a38af9 100644 --- a/TODO.md +++ b/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? diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index e6a375a5..c90065a4 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -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: diff --git a/orchestra/apps/bills/actions.py b/orchestra/apps/bills/actions.py index 0bdbc8ed..0c1bebeb 100644 --- a/orchestra/apps/bills/actions.py +++ b/orchestra/apps/bills/actions.py @@ -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 provide 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 provide 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( + _('One related transaction has been created') % url, + _('%i related transactions 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' diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py index 5c59bf80..88d5ddad 100644 --- a/orchestra/apps/bills/admin.py +++ b/orchestra/apps/bills/admin.py @@ -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,9 +97,14 @@ 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)) - url += '?bill=%i' % bill.pk + 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') return '{name}'.format( @@ -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 provided' % 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) diff --git a/orchestra/apps/bills/helpers.py b/orchestra/apps/bills/helpers.py new file mode 100644 index 00000000..cc0619fb --- /dev/null +++ b/orchestra/apps/bills/helpers.py @@ -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 provide one') + 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 + diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py index 769d14cd..09c489ee 100644 --- a/orchestra/apps/bills/models.py +++ b/orchestra/apps/bills/models.py @@ -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() diff --git a/orchestra/apps/bills/templates/bills/bill-notification.email b/orchestra/apps/bills/templates/bills/bill-notification.email index 52535d6e..425fc490 100644 --- a/orchestra/apps/bills/templates/bills/bill-notification.email +++ b/orchestra/apps/bills/templates/bills/bill-notification.email @@ -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 %} diff --git a/orchestra/apps/orchestration/backends.py b/orchestra/apps/orchestration/backends.py index a5a092ef..911abda7 100644 --- a/orchestra/apps/orchestration/backends.py +++ b/orchestra/apps/orchestration/backends.py @@ -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): diff --git a/orchestra/apps/orders/actions.py b/orchestra/apps/orders/actions.py index 4f8fe13b..f9797efa 100644 --- a/orchestra/apps/orders/actions.py +++ b/orchestra/apps/orders/actions.py @@ -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,14 +79,18 @@ 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]) - url = reverse('admin:bills_bill_changelist') - url += '?id__in=%s' % ids + 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( 'One bill has been created.', diff --git a/orchestra/apps/orders/forms.py b/orchestra/apps/orders/forms.py index 31cce901..c96da35a 100644 --- a/orchestra/apps/orders/forms.py +++ b/orchestra/apps/orders/forms.py @@ -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) diff --git a/orchestra/apps/payments/admin.py b/orchestra/apps/payments/admin.py index f58818d5..843f248f 100644 --- a/orchestra/apps/payments/admin.py +++ b/orchestra/apps/payments/admin.py @@ -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 = '%s' % (url, '
'.join(lines)) + return '%s' % (url, transactions) display_transactions.short_description = _("Transactions") display_transactions.allow_tags = True diff --git a/orchestra/apps/payments/methods/sepadirectdebit.py b/orchestra/apps/payments/methods/sepadirectdebit.py index 9ca73693..f364b0df 100644 --- a/orchestra/apps/payments/methods/sepadirectdebit.py +++ b/orchestra/apps/payments/methods/sepadirectdebit.py @@ -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 diff --git a/orchestra/apps/payments/models.py b/orchestra/apps/payments/models.py index b3684122..3ad2f259 100644 --- a/orchestra/apps/payments/models.py +++ b/orchestra/apps/payments/models.py @@ -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 diff --git a/orchestra/apps/resources/backends.py b/orchestra/apps/resources/backends.py index fae87831..754ccebf 100644 --- a/orchestra/apps/resources/backends.py +++ b/orchestra/apps/resources/backends.py @@ -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): diff --git a/orchestra/apps/saas/services/wordpress.py b/orchestra/apps/saas/services/wordpress.py index fd5bbde5..4f4f66c8 100644 --- a/orchestra/apps/saas/services/wordpress.py +++ b/orchestra/apps/saas/services/wordpress.py @@ -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 = '%s' % (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' diff --git a/orchestra/templates/admin/orchestra/select_plugin.html b/orchestra/templates/admin/orchestra/select_plugin.html index dbe419b6..b5421f5e 100644 --- a/orchestra/templates/admin/orchestra/select_plugin.html +++ b/orchestra/templates/admin/orchestra/select_plugin.html @@ -28,7 +28,7 @@ {% else %} {% endif %} diff --git a/orchestra/utils/plugins.py b/orchestra/utils/plugins.py index bba8312b..a0888fd1 100644 --- a/orchestra/utils/plugins.py +++ b/orchestra/utils/plugins.py @@ -23,31 +23,35 @@ class Plugin(object): return plugin 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 def get_plugin_choices(cls): - plugins = cls.get_plugins() choices = [] - for p in plugins: - # don't evaluate p.verbose_name ugettext_lazy - verbose = getattr(p.verbose_name, '_proxy____args', [p.verbose_name]) - if verbose[0]: - 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]) + 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 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 - # class shouldn't be registered as a plugin. Instead, it sets up a - # list where plugins can be registered later. - cls.plugins = [] - else: - # This must be a plugin implementation, which should be registered. - # Simply appending it to the list is all that's needed to keep - # track of it later. - cls.plugins.append(cls) + 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 + # class shouldn't be registered as a plugin. Instead, it sets up a + # list where plugins can be registered later. + cls.plugins = [] + else: + # This must be a plugin implementation, which should be registered. + # Simply appending it to the list is all that's needed to keep + # track of it later. + cls.plugins.append(cls) diff --git a/scripts/services/mailman.md b/scripts/services/mailman.md new file mode 100644 index 00000000..ef2e467a --- /dev/null +++ b/scripts/services/mailman.md @@ -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 + +