Refactored saas and webapps
This commit is contained in:
parent
02b8e24f45
commit
f3551bb13e
30
TODO.md
30
TODO.md
|
@ -161,23 +161,23 @@
|
|||
|
||||
* Rename apache logs ending on .log in order to logrotate easily
|
||||
|
||||
* SaaS wordpress multiple blogs per user? separate users from sites? SaaSUser SaaSSite models
|
||||
* Custom domains for SaaS apps (wordpress Vhost) SaaSSite.domain ?
|
||||
* multitenant webapps modeled on WepApp -> name unique for all accounts
|
||||
|
||||
* webapp compat webapp-options
|
||||
* webapps modeled on classes instead of settings?
|
||||
|
||||
* Change account and orders
|
||||
|
||||
* Mix webapps type with backends (two for the price of one)
|
||||
|
||||
==== SaaS ====
|
||||
Wordpress
|
||||
---------
|
||||
* site_name
|
||||
* email
|
||||
* site_title
|
||||
* site_domain (optional)
|
||||
* Webapp options and type compatibility
|
||||
|
||||
BSCW
|
||||
----
|
||||
* email
|
||||
* username
|
||||
* quota
|
||||
* password (optional)
|
||||
Multi-tenant WebApps
|
||||
--------------------
|
||||
* SaaS - Those apps that can't use custom domain
|
||||
* WebApp - Those apps that can use custom domain
|
||||
|
||||
* Howto upgrade webapp PHP version? <FilesMatch \.php$> SetHandler php54-cgi</FilesMatch> ? or create a new app
|
||||
|
||||
|
||||
* prevent @pangea.org email addresses on contacts
|
||||
|
|
|
@ -38,7 +38,7 @@ class SendEmail(object):
|
|||
raise PermissionDenied
|
||||
initial={
|
||||
'email_from': self.default_from,
|
||||
'to': ' '.join(self.queryset.values_list('email', flat=True))
|
||||
'to': ' '.join(self.get_queryset_emails())
|
||||
}
|
||||
form = self.form(initial=initial)
|
||||
if request.POST.get('post'):
|
||||
|
@ -63,8 +63,10 @@ class SendEmail(object):
|
|||
# Display confirmation page
|
||||
return render(request, self.template, self.context)
|
||||
|
||||
def get_queryset_emails(self):
|
||||
return self.queryset.value_list('email', flat=True)
|
||||
|
||||
def confirm_email(self, request, **options):
|
||||
num = len(self.queryset)
|
||||
email_from = options['email_from']
|
||||
extra_to = options['extra_to']
|
||||
subject = options['subject']
|
||||
|
@ -72,13 +74,15 @@ class SendEmail(object):
|
|||
# The user has already confirmed
|
||||
if request.POST.get('post') == 'email_confirmation':
|
||||
emails = []
|
||||
for contact in self.queryset.all():
|
||||
emails.append((subject, message, email_from, [contact.email]))
|
||||
num = 0
|
||||
for email in self.get_queryset_emails():
|
||||
emails.append((subject, message, email_from, [email]))
|
||||
num += 1
|
||||
if extra_to:
|
||||
emails.append((subject, message, email_from, extra_to))
|
||||
send_mass_mail(emails)
|
||||
send_mass_mail(emails, fail_silently=False)
|
||||
msg = ungettext(
|
||||
_("Message has been sent to %s.") % str(contact),
|
||||
_("Message has been sent to one %s.") % self.opts.verbose_name_plural,
|
||||
_("Message has been sent to %i %s.") % (num, self.opts.verbose_name_plural),
|
||||
num
|
||||
)
|
||||
|
|
|
@ -8,6 +8,8 @@ from django.utils.translation import ungettext, ugettext_lazy as _
|
|||
from orchestra.admin.decorators import action_with_confirmation
|
||||
from orchestra.core import services
|
||||
|
||||
from . import settings
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@action_with_confirmation()
|
||||
|
@ -38,6 +40,7 @@ list_contacts.verbose_name = _("List contacts")
|
|||
|
||||
|
||||
def service_report(modeladmin, request, queryset):
|
||||
# TODO resources
|
||||
accounts = []
|
||||
fields = []
|
||||
# First we get related manager names to fire a prefetch related
|
||||
|
@ -59,4 +62,4 @@ def service_report(modeladmin, request, queryset):
|
|||
'accounts': accounts,
|
||||
'date': timezone.now().today()
|
||||
}
|
||||
return render(request, 'admin/accounts/account/service_report.html', context)
|
||||
return render(request, settings.ACCOUNTS_SERVICE_REPORT_TEMPLATE, context)
|
||||
|
|
|
@ -47,3 +47,7 @@ ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
|
|||
_("Designates whether to creates a related subdomain <username>.orchestra.lan or not."),
|
||||
),
|
||||
))
|
||||
|
||||
|
||||
ACCOUNTS_SERVICE_REPORT_TEMPLATE = getattr(settings, 'ACCOUNTS_SERVICE_REPORT_TEMPLATE',
|
||||
'admin/accounts/account/service_report.html')
|
||||
|
|
|
@ -54,7 +54,10 @@
|
|||
<li class="item-title">{{ opts.verbose_name_plural|capfirst }}</li>
|
||||
<ul>
|
||||
{% for obj in related %}
|
||||
<li class="related"><a href="{{ obj|admin_url }}">{{ obj }}</a>{% if not obj|isactive %} ({% trans "disabled" %}){% endif %}</li>
|
||||
<li class="related"><a href="{{ obj|admin_url }}">{{ obj }}</a>
|
||||
{% if not obj|isactive %} ({% trans "disabled" %}){% endif %}
|
||||
{{ obj.get_description|capfirst }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
|
|
@ -159,7 +159,8 @@ class Bill(models.Model):
|
|||
return now + relativedelta(months=1)
|
||||
|
||||
def close(self, payment=False):
|
||||
assert self.is_open, "Bill not in Open state"
|
||||
if not self.is_open:
|
||||
raise TypeError("Bill not in Open state.")
|
||||
if payment is False:
|
||||
payment = self.account.paymentsources.get_default()
|
||||
if not self.due_on:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||
|
||||
from orchestra.core import services
|
||||
from orchestra.core.validators import validate_ipv4_address, validate_ipv6_address, validate_ascii
|
||||
|
@ -50,6 +50,15 @@ class Domain(models.Model):
|
|||
def subdomains(self):
|
||||
return Domain.objects.filter(name__regex='\.%s$' % self.name)
|
||||
|
||||
def get_description(self):
|
||||
if self.is_top:
|
||||
num = self.subdomains.count()
|
||||
return ungettext(
|
||||
_("top domain with one subdomain"),
|
||||
_("top domain with %d subdomains") % num,
|
||||
num)
|
||||
return _("subdomain")
|
||||
|
||||
def get_absolute_url(self):
|
||||
return 'http://%s' % self.name
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ class MailmanBackend(ServiceController):
|
|||
[[ $(grep "^\s*%(address_domain)s\s*$" %(virtual_alias_domains)s) ]] || {
|
||||
echo "%(address_domain)s" >> %(virtual_alias_domains)s
|
||||
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
|
||||
}""" % context
|
||||
))
|
||||
}""") % context
|
||||
)
|
||||
|
||||
def exclude_virtual_alias_domain(self, context):
|
||||
address_domain = context['address_domain']
|
||||
|
@ -58,13 +58,11 @@ class MailmanBackend(ServiceController):
|
|||
self.append(textwrap.dedent("""\
|
||||
[[ ! -e %(mailman_root)s/lists/%(name)s ]] && {
|
||||
newlist --quiet --emailhost='%(domain)s' '%(name)s' '%(admin)s' '%(password)s'
|
||||
}""" % context))
|
||||
}""") % context)
|
||||
# Custom domain
|
||||
if mail_list.address:
|
||||
aliases = self.get_virtual_aliases(context)
|
||||
context['aliases'] = self.get_virtual_aliases(context)
|
||||
# Preserve indentation
|
||||
spaces = ' '*4
|
||||
context['aliases'] = spaces + aliases.replace('\n', '\n'+spaces)
|
||||
self.append(textwrap.dedent("""\
|
||||
if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
||||
echo '# %(banner)s\n%(aliases)s
|
||||
|
@ -78,23 +76,28 @@ class MailmanBackend(ServiceController):
|
|||
' >> %(virtual_alias)s
|
||||
UPDATED_VIRTUAL_ALIAS=1
|
||||
fi
|
||||
fi""" % context
|
||||
))
|
||||
self.append('echo "require_explicit_destination = 0" | '
|
||||
'%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s' % context)
|
||||
fi""") % context
|
||||
)
|
||||
self.append(
|
||||
'echo "require_explicit_destination = 0" | '
|
||||
'%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s' % context
|
||||
)
|
||||
self.append(textwrap.dedent("""\
|
||||
echo "host_name = '%(address_domain)s'" | \
|
||||
%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s""" % context))
|
||||
%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s""") % context
|
||||
)
|
||||
else:
|
||||
# Cleanup shit
|
||||
self.append(textwrap.dedent("""\
|
||||
if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
||||
sed -i "/^.*\s%(name)s\s*$/d" %(virtual_alias)s
|
||||
fi""" % context
|
||||
))
|
||||
fi""") % context
|
||||
)
|
||||
# Update
|
||||
if context['password'] is not None:
|
||||
self.append('%(mailman_root)s/bin/change_pw --listname="%(name)s" --password="%(password)s"' % context)
|
||||
self.append(
|
||||
'%(mailman_root)s/bin/change_pw --listname="%(name)s" --password="%(password)s"' % context
|
||||
)
|
||||
self.include_virtual_alias_domain(context)
|
||||
|
||||
def delete(self, mail_list):
|
||||
|
@ -102,8 +105,8 @@ class MailmanBackend(ServiceController):
|
|||
self.exclude_virtual_alias_domain(context)
|
||||
self.append(textwrap.dedent("""\
|
||||
sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
|
||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s""" % context
|
||||
))
|
||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s""") % context
|
||||
)
|
||||
self.append("rmlist -a %(name)s" % context)
|
||||
|
||||
def commit(self):
|
||||
|
@ -111,8 +114,8 @@ class MailmanBackend(ServiceController):
|
|||
self.append(textwrap.dedent("""
|
||||
[[ $UPDATED_VIRTUAL_ALIAS == 1 ]] && { postmap %(virtual_alias)s; }
|
||||
[[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]] && { /etc/init.d/postfix reload; }
|
||||
""" % context
|
||||
))
|
||||
""") % context
|
||||
)
|
||||
|
||||
def get_context_files(self):
|
||||
return {
|
||||
|
@ -163,7 +166,7 @@ class MailmanTraffic(ServiceMonitor):
|
|||
| tr '\\n' '+' \\
|
||||
| xargs -i echo {} )
|
||||
echo ${OBJECT_ID} $(( ${SIZE}*${SUBSCRIBERS} ))
|
||||
}""" % current_date))
|
||||
}""") % current_date)
|
||||
|
||||
def monitor(self, mail_list):
|
||||
context = self.get_context(mail_list)
|
||||
|
|
7
orchestra/apps/mailboxes/actions.py
Normal file
7
orchestra/apps/mailboxes/actions.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from orchestra.admin.actions import SendEmail
|
||||
|
||||
|
||||
class SendMailboxEmail(SendEmail):
|
||||
def get_queryset_emails(self):
|
||||
for mailbox in self.queryset.all():
|
||||
yield mailbox.get_local_address()
|
|
@ -11,6 +11,8 @@ from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
|||
from orchestra.admin.utils import admin_link, change_url
|
||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin, AccountAdminMixin
|
||||
|
||||
from . import settings
|
||||
from .actions import SendMailboxEmail
|
||||
from .filters import HasMailboxListFilter, HasForwardListFilter, HasAddressListFilter
|
||||
from .forms import MailboxCreationForm, MailboxChangeForm, AddressForm
|
||||
from .models import Mailbox, Address, Autoresponse
|
||||
|
@ -71,6 +73,13 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
|||
display_addresses.short_description = _("Addresses")
|
||||
display_addresses.allow_tags = True
|
||||
|
||||
def get_actions(self, request):
|
||||
if settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN:
|
||||
self.actions = (SendMailboxEmail(),)
|
||||
else:
|
||||
self.actions = ()
|
||||
return super(MailboxAdmin, self).get_actions(request)
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fieldsets = super(MailboxAdmin, self).get_fieldsets(request, obj=obj)
|
||||
if obj and obj.filtering == obj.CUSTOM:
|
||||
|
|
|
@ -89,10 +89,10 @@ class PasswdVirtualUserBackend(ServiceController):
|
|||
context = {
|
||||
'virtual_mailbox_maps': settings.MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH
|
||||
}
|
||||
self.append(
|
||||
"[[ $UPDATED_VIRTUAL_MAILBOX_MAPS == 1 ]] && { postmap %(virtual_mailbox_maps)s; }"
|
||||
% context
|
||||
)
|
||||
self.append(textwrap.dedent("""\
|
||||
[[ $UPDATED_VIRTUAL_MAILBOX_MAPS == 1 ]] && {
|
||||
postmap %(virtual_mailbox_maps)s
|
||||
}""" % context))
|
||||
|
||||
def get_context(self, mailbox):
|
||||
context = {
|
||||
|
@ -123,8 +123,7 @@ class PostfixAddressBackend(ServiceController):
|
|||
[[ $(grep "^\s*%(domain)s\s*$" %(virtual_alias_domains)s) ]] || {
|
||||
echo "%(domain)s" >> %(virtual_alias_domains)s
|
||||
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
|
||||
}""" % context
|
||||
))
|
||||
}""") % context)
|
||||
|
||||
def exclude_virtual_alias_domain(self, context):
|
||||
domain = context['domain']
|
||||
|
@ -151,8 +150,7 @@ class PostfixAddressBackend(ServiceController):
|
|||
sed -i "s/^%(email)s\s.*$/${LINE}/" %(virtual_alias_maps)s
|
||||
UPDATED_VIRTUAL_ALIAS_MAPS=1
|
||||
fi
|
||||
fi""" % context
|
||||
))
|
||||
fi""") % context)
|
||||
else:
|
||||
logger.warning("Address %i is empty" % address.pk)
|
||||
self.append('sed -i "/^%(email)s\s/d" %(virtual_alias_maps)s')
|
||||
|
@ -163,8 +161,7 @@ class PostfixAddressBackend(ServiceController):
|
|||
if [[ $(grep "^%(email)s\s" %(virtual_alias_maps)s) ]]; then
|
||||
sed -i "/^%(email)s\s.*$/d" %(virtual_alias_maps)s
|
||||
UPDATED_VIRTUAL_ALIAS_MAPS=1
|
||||
fi""" % context
|
||||
))
|
||||
fi""") % context)
|
||||
|
||||
def save(self, address):
|
||||
context = self.get_context(address)
|
||||
|
@ -181,8 +178,8 @@ class PostfixAddressBackend(ServiceController):
|
|||
self.append(textwrap.dedent("""
|
||||
[[ $UPDATED_VIRTUAL_ALIAS_MAPS == 1 ]] && { postmap %(virtual_alias_maps)s; }
|
||||
[[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]] && { /etc/init.d/postfix reload; }
|
||||
""" % context
|
||||
))
|
||||
""") % context
|
||||
)
|
||||
|
||||
def get_context_files(self):
|
||||
return {
|
||||
|
|
|
@ -77,6 +77,11 @@ class Mailbox(models.Model):
|
|||
address.delete()
|
||||
else:
|
||||
address.save()
|
||||
|
||||
def get_local_address(self):
|
||||
if not settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN:
|
||||
raise AttributeError("Mailboxes do not have a defined local address domain")
|
||||
return '@'.join((self.name, settings.MAILBOXES_LOCAL_ADDRESS_DOMAIN))
|
||||
|
||||
|
||||
class Address(models.Model):
|
||||
|
|
|
@ -61,7 +61,12 @@ MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS',
|
|||
})
|
||||
|
||||
|
||||
MAILBOXES_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILBOXES_MAILBOX_DEFAULT_FILTERING', 'REDIRECT')
|
||||
MAILBOXES_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILBOXES_MAILBOX_DEFAULT_FILTERING',
|
||||
'REDIRECT')
|
||||
|
||||
|
||||
MAILBOXES_MAILDIRSIZE_PATH = getattr(settings, 'MAILBOXES_MAILDIRSIZE_PATH', '%(home)s/Maildir/maildirsize')
|
||||
|
||||
|
||||
MAILBOXES_LOCAL_ADDRESS_DOMAIN = getattr(settings, 'MAILBOXES_LOCAL_ADDRESS_DOMAIN',
|
||||
'orchestra.lan')
|
||||
|
|
|
@ -60,6 +60,9 @@ class Miscellaneous(models.Model):
|
|||
except type(self).account.field.rel.to.DoesNotExist:
|
||||
return self.is_active
|
||||
|
||||
def get_description(self):
|
||||
return ' '.join((str(self.amount), self.service.description or self.service.verbose_name))
|
||||
|
||||
def clean(self):
|
||||
if self.identifier:
|
||||
self.identifier = self.identifier.strip()
|
||||
|
|
|
@ -10,6 +10,14 @@ from orchestra import plugins
|
|||
from . import methods
|
||||
|
||||
|
||||
class ServiceMount(plugins.PluginMount):
|
||||
def __init__(cls, name, bases, attrs):
|
||||
# Make sure backends specify a model attribute
|
||||
if not (attrs.get('abstract', False) or name == 'ServiceBackend' or cls.model):
|
||||
raise AttributeError("'%s' does not have a defined model attribute." % cls)
|
||||
super(ServiceMount, cls).__init__(name, bases, attrs)
|
||||
|
||||
|
||||
class ServiceBackend(plugins.Plugin):
|
||||
"""
|
||||
Service management backend base class
|
||||
|
@ -27,7 +35,7 @@ class ServiceBackend(plugins.Plugin):
|
|||
ignore_fields = []
|
||||
actions = []
|
||||
|
||||
__metaclass__ = plugins.PluginMount
|
||||
__metaclass__ = ServiceMount
|
||||
|
||||
def __unicode__(self):
|
||||
return type(self).__name__
|
||||
|
|
|
@ -50,10 +50,11 @@ def SSH(backend, log, server, cmds, async=False):
|
|||
key = settings.ORCHESTRATION_SSH_KEY_PATH
|
||||
try:
|
||||
ssh.connect(addr, username='root', key_filename=key, timeout=10)
|
||||
except socket.error:
|
||||
logger.error('%s timed out on %s' % (backend, server))
|
||||
except socket.error, e:
|
||||
logger.error('%s timed out on %s' % (backend, addr))
|
||||
log.state = log.TIMEOUT
|
||||
log.save(update_fields=['state'])
|
||||
log.stderr = str(e)
|
||||
log.save(update_fields=['state', 'stderr'])
|
||||
return
|
||||
transport = ssh.get_transport()
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ STATE_COLORS = {
|
|||
class PaymentSourceAdmin(SelectPluginAdminMixin, AccountAdminMixin, admin.ModelAdmin):
|
||||
list_display = ('label', 'method', 'number', 'account_link', 'is_active')
|
||||
list_filter = ('method', 'is_active')
|
||||
search_fields = ('account__username', 'account__full_name', 'data')
|
||||
plugin = PaymentMethod
|
||||
plugin_field = 'method'
|
||||
|
||||
|
|
|
@ -119,23 +119,27 @@ class Transaction(models.Model):
|
|||
if amount >= self.bill.total:
|
||||
raise ValidationError(_("New transactions can not be allocated for this bill."))
|
||||
|
||||
def check_state(*args):
|
||||
if self.state not in args:
|
||||
raise TypeError("Transaction not in %s" % ' or '.join(args))
|
||||
|
||||
def mark_as_processed(self):
|
||||
assert self.state == self.WAITTING_PROCESSING
|
||||
self.check_state(self.WAITTING_PROCESSING)
|
||||
self.state = self.WAITTING_EXECUTION
|
||||
self.save(update_fields=['state'])
|
||||
|
||||
def mark_as_executed(self):
|
||||
assert self.state == self.WAITTING_EXECUTION
|
||||
self.check_state(self.WAITTING_EXECUTION)
|
||||
self.state = self.EXECUTED
|
||||
self.save(update_fields=['state'])
|
||||
|
||||
def mark_as_secured(self):
|
||||
assert self.state == self.EXECUTED
|
||||
self.check_state(self.EXECUTED)
|
||||
self.state = self.SECURED
|
||||
self.save(update_fields=['state'])
|
||||
|
||||
def mark_as_rejected(self):
|
||||
assert self.state == self.EXECUTED
|
||||
self.check_state(self.EXECUTED)
|
||||
self.state = self.REJECTED
|
||||
self.save(update_fields=['state'])
|
||||
|
||||
|
@ -167,22 +171,26 @@ class TransactionProcess(models.Model):
|
|||
def __unicode__(self):
|
||||
return '#%i' % self.id
|
||||
|
||||
def check_state(*args):
|
||||
if self.state not in args:
|
||||
raise TypeError("Transaction process not in %s" % ' or '.join(args))
|
||||
|
||||
def mark_as_executed(self):
|
||||
assert self.state == self.CREATED
|
||||
self.check_state(self.CREATED)
|
||||
self.state = self.EXECUTED
|
||||
for transaction in self.transactions.all():
|
||||
transaction.mark_as_executed()
|
||||
self.save(update_fields=['state'])
|
||||
|
||||
def abort(self):
|
||||
assert self.state in [self.CREATED, self.EXCECUTED]
|
||||
self.check_state(self.CREATED, self.EXCECUTED)
|
||||
self.state = self.ABORTED
|
||||
for transaction in self.transaction.all():
|
||||
transaction.mark_as_aborted()
|
||||
self.save(update_fields=['state'])
|
||||
|
||||
def commit(self):
|
||||
assert self.state in [self.CREATED, self.EXECUTED]
|
||||
self.check_state(self.CREATED, self.EXECUTED)
|
||||
self.state = self.COMMITED
|
||||
for transaction in self.transactions.processing():
|
||||
transaction.mark_as_secured()
|
||||
|
|
|
@ -9,33 +9,32 @@ from django.utils.translation import ungettext, ugettext_lazy as _
|
|||
def run_monitor(modeladmin, request, queryset):
|
||||
""" Resource and ResourceData run monitors """
|
||||
referer = request.META.get('HTTP_REFERER')
|
||||
if not queryset:
|
||||
modeladmin.message_user(request, _("No resource has been selected,"))
|
||||
return redirect(referer)
|
||||
async = modeladmin.model.monitor.func_defaults[0]
|
||||
results = []
|
||||
logs = set()
|
||||
for resource in queryset:
|
||||
result = resource.monitor()
|
||||
results = resource.monitor()
|
||||
if not async:
|
||||
results += result
|
||||
for result in results:
|
||||
if hasattr(result, 'log'):
|
||||
logs.add(result.log.pk)
|
||||
modeladmin.log_change(request, resource, _("Run monitors"))
|
||||
num = len(queryset)
|
||||
if async:
|
||||
num = len(queryset)
|
||||
link = reverse('admin:djcelery_taskstate_changelist')
|
||||
msg = ungettext(
|
||||
_("One selected resource has been <a href='%s'>scheduled for monitoring</a>.") % link,
|
||||
_("%s selected resource have been <a href='%s'>scheduled for monitoring</a>.") % (num, link),
|
||||
num)
|
||||
else:
|
||||
if len(results) == 1:
|
||||
log = results[0].log
|
||||
link = reverse('admin:orchestration_backendlog_change', args=(log.pk,))
|
||||
msg = _("One selected resource has <a href='%s'>been monitored</a>.") % link
|
||||
elif len(results) >= 1:
|
||||
logs = [str(result.log.pk) for result in results]
|
||||
num = len(logs)
|
||||
if num == 1:
|
||||
log = logs.pop()
|
||||
link = reverse('admin:orchestration_backendlog_change', args=(log,))
|
||||
msg = _("One related monitor has <a href='%s'>been executed</a>.") % link
|
||||
elif num >= 1:
|
||||
link = reverse('admin:orchestration_backendlog_changelist')
|
||||
link += '?id__in=%s' % ','.join(logs)
|
||||
msg = _("%s selected resources have <a href='%s'>been monitored</a>.") % (num, link)
|
||||
msg = _("%s related monitors have <a href='%s'>been executed</a>.") % (num, link)
|
||||
else:
|
||||
msg = _("No related monitors have been executed.")
|
||||
modeladmin.message_user(request, mark_safe(msg))
|
||||
|
|
|
@ -119,8 +119,7 @@ class Resource(models.Model):
|
|||
def get_model_path(self, monitor):
|
||||
""" returns a model path between self.content_type and monitor.model """
|
||||
resource_model = self.content_type.model_class()
|
||||
model_path = ServiceMonitor.get_backend(monitor).model
|
||||
monitor_model = get_model(model_path)
|
||||
monitor_model = ServiceMonitor.get_backend(monitor).model_class()
|
||||
return get_model_field_path(monitor_model, resource_model)
|
||||
|
||||
def sync_periodic_task(self):
|
||||
|
@ -223,6 +222,7 @@ class ResourceData(models.Model):
|
|||
)
|
||||
else:
|
||||
fields = '__'.join(path)
|
||||
monitor_model = ServiceMonitor.get_backend(monitor).model_class()
|
||||
objects = monitor_model.objects.filter(**{fields: self.object_id})
|
||||
pks = objects.values_list('id', flat=True)
|
||||
ct = ContentType.objects.get_for_model(monitor_model)
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import re
|
||||
|
||||
import requests
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.orchestration import ServiceController
|
||||
|
||||
from . import SaaSServiceMixin
|
||||
from .. import settings
|
||||
|
||||
|
||||
class WordpressMuBackend(SaaSServiceMixin, ServiceController):
|
||||
verbose_name = _("Wordpress multisite")
|
||||
|
||||
@property
|
||||
def script(self):
|
||||
return self.cmds
|
||||
|
||||
def login(self, session):
|
||||
base_url = self.get_base_url()
|
||||
login_url = base_url + '/wp-login.php'
|
||||
login_data = {
|
||||
'log': 'admin',
|
||||
'pwd': settings.WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD,
|
||||
'redirect_to': '/wp-admin/'
|
||||
}
|
||||
response = session.post(login_url, data=login_data)
|
||||
if response.url != base_url + '/wp-admin/':
|
||||
raise IOError("Failure login to remote application")
|
||||
|
||||
def get_base_url(self):
|
||||
base_url = settings.WEBAPPS_WORDPRESSMU_BASE_URL
|
||||
if base_url.endswith('/'):
|
||||
base_url = base_url[:-1]
|
||||
return base_url
|
||||
|
||||
def create_blog(self, webapp, server):
|
||||
emails = webapp.account.contacts.filter(email_usage__contains='')
|
||||
email = emails.values_list('email', flat=True).first()
|
||||
|
||||
base_url = self.get_base_url()
|
||||
session = requests.Session()
|
||||
self.login(session)
|
||||
|
||||
url = base_url + '/wp-admin/network/site-new.php'
|
||||
content = session.get(url).content
|
||||
wpnonce = re.compile('name="_wpnonce_add-blog"\s+value="([^"]*)"')
|
||||
wpnonce = wpnonce.search(content).groups()[0]
|
||||
|
||||
url += '?action=add-site'
|
||||
data = {
|
||||
'blog[domain]': webapp.name,
|
||||
'blog[title]': webapp.name,
|
||||
'blog[email]': email,
|
||||
'_wpnonce_add-blog': wpnonce,
|
||||
}
|
||||
# TODO validate response
|
||||
response = session.post(url, data=data)
|
||||
|
||||
def delete_blog(self, webapp, server):
|
||||
# OH, I've enjoied so much coding this methods that I want to thanks
|
||||
# the wordpress team for the excellent software they are producing
|
||||
context = self.get_context(webapp)
|
||||
session = requests.Session()
|
||||
self.login(session)
|
||||
|
||||
base_url = self.get_base_url()
|
||||
search = base_url + '/wp-admin/network/sites.php?s=%(name)s&action=blogs' % context
|
||||
regex = re.compile(
|
||||
'<a href="http://[\.\-\w]+/wp-admin/network/site-info\.php\?id=([0-9]+)"\s+'
|
||||
'class="edit">%(name)s</a>' % context
|
||||
)
|
||||
content = session.get(search).content
|
||||
ids = regex.search(content).groups()
|
||||
if len(ids) > 1:
|
||||
raise ValueError("Multiple matches")
|
||||
|
||||
delete = re.compile('<span class="delete">(.*)</span>')
|
||||
content = delete.search(content).groups()[0]
|
||||
wpnonce = re.compile('_wpnonce=([^"]*)"')
|
||||
wpnonce = wpnonce.search(content).groups()[0]
|
||||
delete = '/wp-admin/network/sites.php?action=confirm&action2=deleteblog'
|
||||
delete += '&id=%d&_wpnonce=%d' % (ids[0], wpnonce)
|
||||
|
||||
content = session.get(delete).content
|
||||
wpnonce = re.compile('name="_wpnonce"\s+value="([^"]*)"')
|
||||
wpnonce = wpnonce.search(content).groups()[0]
|
||||
data = {
|
||||
'action': 'deleteblog',
|
||||
'id': ids[0],
|
||||
'_wpnonce': wpnonce,
|
||||
'_wp_http_referer': '/wp-admin/network/sites.php',
|
||||
}
|
||||
delete = base_url + '/wp-admin/network/sites.php?action=deleteblog'
|
||||
session.post(delete, data=data)
|
||||
|
||||
def save(self, webapp):
|
||||
self.append(self.create_blog, webapp)
|
||||
|
||||
def delete(self, webapp):
|
||||
self.append(self.delete_blog, webapp)
|
|
@ -1,6 +0,0 @@
|
|||
from .options import SoftwareService
|
||||
|
||||
|
||||
class DokuwikiService(SoftwareService):
|
||||
verbose_name = "Dowkuwiki"
|
||||
icon = 'saas/icons/Dokuwiki.png'
|
|
@ -1,6 +0,0 @@
|
|||
from .options import SoftwareService
|
||||
|
||||
|
||||
class DrupalService(SoftwareService):
|
||||
verbose_name = "Drupal"
|
||||
icon = 'saas/icons/Drupal.png'
|
|
@ -1,23 +0,0 @@
|
|||
from django import forms
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from .options import SoftwareService, SoftwareServiceForm
|
||||
|
||||
|
||||
class WordPressForm(SoftwareServiceForm):
|
||||
email = forms.EmailField(label=_("Email"), widget=forms.TextInput(attrs={'size':'40'}))
|
||||
|
||||
|
||||
class WordPressDataSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField(label=_("Email"))
|
||||
|
||||
|
||||
class WordPressService(SoftwareService):
|
||||
verbose_name = "WordPress"
|
||||
form = WordPressForm
|
||||
serializer = WordPressDataSerializer
|
||||
icon = 'saas/icons/WordPress.png'
|
||||
site_name_base_domain = 'blogs.orchestra.lan'
|
||||
change_readonly_fileds = ('email',)
|
|
@ -2,31 +2,8 @@ from django.conf import settings
|
|||
|
||||
|
||||
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
|
||||
'orchestra.apps.saas.services.wordpress.WordPressService',
|
||||
'orchestra.apps.saas.services.drupal.DrupalService',
|
||||
'orchestra.apps.saas.services.dokuwiki.DokuwikiService',
|
||||
'orchestra.apps.saas.services.moodle.MoodleService',
|
||||
'orchestra.apps.saas.services.bscw.BSCWService',
|
||||
'orchestra.apps.saas.services.gitlab.GitLabService',
|
||||
'orchestra.apps.saas.services.phplist.PHPListService',
|
||||
))
|
||||
|
||||
|
||||
SAAS_WORDPRESSMU_BASE_URL = getattr(settings, 'SAAS_WORDPRESSMU_BASE_URL',
|
||||
'http://%(site_name)s.example.com')
|
||||
|
||||
|
||||
SAAS_WORDPRESSMU_ADMIN_PASSWORD = getattr(settings, 'SAAS_WORDPRESSMU_ADMIN_PASSWORD',
|
||||
'secret')
|
||||
|
||||
|
||||
SAAS_DOKUWIKIMU_TEMPLATE_PATH = setattr(settings, 'SAAS_DOKUWIKIMU_TEMPLATE_PATH',
|
||||
'/home/httpd/htdocs/wikifarm/template.tar.gz')
|
||||
|
||||
|
||||
SAAS_DOKUWIKIMU_FARM_PATH = getattr(settings, 'SAAS_DOKUWIKIMU_FARM_PATH',
|
||||
'/home/httpd/htdocs/wikifarm/farm')
|
||||
|
||||
|
||||
SAAS_DRUPAL_SITES_PATH = getattr(settings, 'SAAS_DRUPAL_SITES_PATH',
|
||||
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s')
|
||||
|
|
|
@ -66,13 +66,17 @@ class SystemUser(models.Model):
|
|||
def has_shell(self):
|
||||
return self.shell not in settings.SYSTEMUSERS_DISABLED_SHELLS
|
||||
|
||||
def get_description(self):
|
||||
return self.get_shell_display()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.home:
|
||||
self.home = self.get_base_home()
|
||||
super(SystemUser, self).save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
self.home = os.path.normpath(self.home)
|
||||
if self.home:
|
||||
self.home = os.path.normpath(self.home)
|
||||
if self.directory:
|
||||
directory_error = None
|
||||
if self.has_shell:
|
||||
|
@ -119,10 +123,7 @@ class SystemUser(models.Model):
|
|||
return os.path.normpath(settings.SYSTEMUSERS_HOME % context)
|
||||
|
||||
def get_home(self):
|
||||
return os.path.join(
|
||||
self.home or self.get_base_home(),
|
||||
self.directory
|
||||
)
|
||||
return os.path.join(self.home, self.directory)
|
||||
|
||||
|
||||
services.register(SystemUser)
|
||||
|
|
|
@ -4,12 +4,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from orchestra.apps.orchestration import ServiceController
|
||||
|
||||
from . import SaaSServiceMixin
|
||||
from .. import settings
|
||||
|
||||
|
||||
class DokuWikiMuBackend(SaaSServiceMixin, ServiceController):
|
||||
class DokuWikiMuBackend(ServiceController):
|
||||
verbose_name = _("DokuWiki multisite")
|
||||
model = 'webapps.WebApp'
|
||||
|
||||
def save(self, webapp):
|
||||
context = self.get_context(webapp)
|
|
@ -1,30 +1,32 @@
|
|||
import os
|
||||
import textwrap
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.orchestration import ServiceController
|
||||
|
||||
from . import SaaSServiceMixin
|
||||
from .. import settings
|
||||
|
||||
|
||||
class DrupalMuBackend(SaaSServiceMixin, ServiceController):
|
||||
class DrupalMuBackend(ServiceController):
|
||||
verbose_name = _("Drupal multisite")
|
||||
|
||||
model = 'webapps.WebApp'
|
||||
|
||||
def save(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
self.append("mkdir %(drupal_path)s" % context)
|
||||
self.append("chown -R www-data %(drupal_path)s" % context)
|
||||
self.append(
|
||||
"# the following assumes settings.php to be previously configured\n"
|
||||
"REGEX='^\s*$databases\[.default.\]\[.default.\]\[.prefix.\]'\n"
|
||||
"CONFIG='$databases[\'default\'][\'default\'][\'prefix\'] = \'%(app_name)s_\';'\n"
|
||||
"if [[ ! $(grep $REGEX %(drupal_settings)s) ]]; then\n"
|
||||
" echo $CONFIG >> %(drupal_settings)s\n"
|
||||
"fi" % context
|
||||
self.append(textwrap.dedent("""\
|
||||
mkdir %(drupal_path)s
|
||||
chown -R www-data %(drupal_path)s
|
||||
|
||||
# the following assumes settings.php to be previously configured
|
||||
REGEX='^\s*$databases\[.default.\]\[.default.\]\[.prefix.\]'
|
||||
CONFIG='$databases[\'default\'][\'default\'][\'prefix\'] = \'%(app_name)s_\';'
|
||||
if [[ ! $(grep $REGEX %(drupal_settings)s) ]]; then
|
||||
echo $CONFIG >> %(drupal_settings)s
|
||||
fi""") % context
|
||||
)
|
||||
|
||||
def selete(self, webapp):
|
||||
def delete(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
self.append("rm -fr %(app_path)s" % context)
|
||||
|
124
orchestra/apps/webapps/backends/wordpressmu.py
Normal file
124
orchestra/apps/webapps/backends/wordpressmu.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
import re
|
||||
|
||||
import requests
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.orchestration import ServiceController
|
||||
|
||||
from .. import settings
|
||||
|
||||
|
||||
class WordpressMuBackend(ServiceController):
|
||||
verbose_name = _("Wordpress multisite")
|
||||
model = 'webapps.WebApp'
|
||||
|
||||
@property
|
||||
def script(self):
|
||||
return self.cmds
|
||||
|
||||
def login(self, session):
|
||||
base_url = self.get_base_url()
|
||||
login_url = base_url + '/wp-login.php'
|
||||
login_data = {
|
||||
'log': 'admin',
|
||||
'pwd': settings.WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD,
|
||||
'redirect_to': '/wp-admin/'
|
||||
}
|
||||
response = session.post(login_url, data=login_data)
|
||||
if response.url != base_url + '/wp-admin/':
|
||||
raise IOError("Failure login to remote application")
|
||||
|
||||
def get_base_url(self):
|
||||
base_url = settings.WEBAPPS_WORDPRESSMU_BASE_URL
|
||||
return base_url.rstrip('/')
|
||||
|
||||
def validate_response(self, response):
|
||||
if response.status_code != 200:
|
||||
errors = re.findall(r'<body id="error-page">\n\t<p>(.*)</p></body>', response.content)
|
||||
raise RuntimeError(errors[0] if errors else 'Unknown %i error' % response.status_code)
|
||||
|
||||
def get_id(self, session, webapp):
|
||||
search = self.get_base_url()
|
||||
search += '/wp-admin/network/sites.php?s=%s&action=blogs' % webapp.name
|
||||
regex = re.compile(
|
||||
'<a href="http://[\.\-\w]+/wp-admin/network/site-info\.php\?id=([0-9]+)"\s+'
|
||||
'class="edit">%s</a>' % webapp.name
|
||||
)
|
||||
content = session.get(search).content
|
||||
# Get id
|
||||
ids = regex.search(content)
|
||||
if not ids:
|
||||
raise RuntimeError("Blog '%s' not found" % webapp.name)
|
||||
ids = ids.groups()
|
||||
if len(ids) > 1:
|
||||
raise ValueError("Multiple matches")
|
||||
# Get wpnonce
|
||||
wpnonce = re.search(r'<span class="delete">(.*)</span>', content).groups()[0]
|
||||
wpnonce = re.search(r'_wpnonce=([^"]*)"', wpnonce).groups()[0]
|
||||
return int(ids[0]), wpnonce
|
||||
|
||||
def create_blog(self, webapp, server):
|
||||
session = requests.Session()
|
||||
self.login(session)
|
||||
|
||||
# Check if blog already exists
|
||||
try:
|
||||
self.get_id(session, webapp)
|
||||
except RuntimeError:
|
||||
url = self.get_base_url()
|
||||
url += '/wp-admin/network/site-new.php'
|
||||
content = session.get(url).content
|
||||
|
||||
wpnonce = re.compile('name="_wpnonce_add-blog"\s+value="([^"]*)"')
|
||||
wpnonce = wpnonce.search(content).groups()[0]
|
||||
|
||||
url += '?action=add-site'
|
||||
data = {
|
||||
'blog[domain]': webapp.name,
|
||||
'blog[title]': webapp.name,
|
||||
'blog[email]': webapp.account.email,
|
||||
'_wpnonce_add-blog': wpnonce,
|
||||
}
|
||||
|
||||
# Validate response
|
||||
response = session.post(url, data=data)
|
||||
self.validate_response(response)
|
||||
|
||||
def delete_blog(self, webapp, server):
|
||||
# OH, I've enjoied so much coding this methods that I want to thanks
|
||||
# the wordpress team for the excellent software they are producing
|
||||
session = requests.Session()
|
||||
self.login(session)
|
||||
|
||||
try:
|
||||
id, wpnonce = self.get_id(session, webapp)
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
delete = self.get_base_url()
|
||||
delete += '/wp-admin/network/sites.php?action=confirm&action2=deleteblog'
|
||||
delete += '&id=%d&_wpnonce=%s' % (id, wpnonce)
|
||||
|
||||
content = session.get(delete).content
|
||||
wpnonce = re.compile('name="_wpnonce"\s+value="([^"]*)"')
|
||||
wpnonce = wpnonce.search(content).groups()[0]
|
||||
data = {
|
||||
'action': 'deleteblog',
|
||||
'id': id,
|
||||
'_wpnonce': wpnonce,
|
||||
'_wp_http_referer': '/wp-admin/network/sites.php',
|
||||
}
|
||||
delete = self.get_base_url()
|
||||
delete += '/wp-admin/network/sites.php?action=deleteblog'
|
||||
response = session.post(delete, data=data)
|
||||
self.validate_response(response)
|
||||
|
||||
def save(self, webapp):
|
||||
if webapp.type != 'wordpress-mu':
|
||||
return
|
||||
self.append(self.create_blog, webapp)
|
||||
|
||||
def delete(self, webapp):
|
||||
if webapp.type != 'wordpress-mu':
|
||||
return
|
||||
self.append(self.delete_blog, webapp)
|
|
@ -29,10 +29,29 @@ class WebApp(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.get_name()
|
||||
|
||||
def get_description(self):
|
||||
return self.get_type_display()
|
||||
|
||||
def clean(self):
|
||||
# Validate unique webapp names
|
||||
if self.app_type.get('unique_name', False):
|
||||
try:
|
||||
webapp = WebApp.objects.exclude(id=self.pk).get(name=self.name, type=self.type)
|
||||
except WebApp.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise ValidationError({
|
||||
'name': _("A webapp with this name already exists."),
|
||||
})
|
||||
|
||||
@cached
|
||||
def get_options(self):
|
||||
return { opt.name: opt.value for opt in self.options.all() }
|
||||
|
||||
@property
|
||||
def app_type(self):
|
||||
return settings.WEBAPPS_TYPES[self.type]
|
||||
|
||||
def get_name(self):
|
||||
return self.name or settings.WEBAPPS_BLANK_NAME
|
||||
|
||||
|
@ -40,7 +59,7 @@ class WebApp(models.Model):
|
|||
return settings.WEBAPPS_FPM_START_PORT + self.account_id
|
||||
|
||||
def get_directive(self):
|
||||
directive = settings.WEBAPPS_TYPES[self.type]['directive']
|
||||
directive = self.app_type['directive']
|
||||
args = directive[1:] if len(directive) > 1 else ()
|
||||
return directive[0], args
|
||||
|
||||
|
|
|
@ -59,6 +59,26 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', {
|
|||
'help_text': _("This creates a Webalizer application under "
|
||||
"~/webapps/<app_name>-<site_name>")
|
||||
},
|
||||
'wordpress-mu': {
|
||||
'verbose_name': _("Wordpress (SaaS)"),
|
||||
'directive': ('fpm', 'fcgi://127.0.0.1:8990/home/httpd/wordpress-mu/'),
|
||||
'help_text': _("This creates a Wordpress site on a multi-tenant Wordpress server.<br>"
|
||||
"By default this blog is accessible via <app_name>.blogs.orchestra.lan")
|
||||
},
|
||||
'dokuwiki-mu': {
|
||||
'verbose_name': _("DokuWiki (SaaS)"),
|
||||
'directive': ('alias', '/home/httpd/wikifarm/farm/'),
|
||||
'help_text': _("This create a Dokuwiki wiki into a shared Dokuwiki server.<br>"
|
||||
"By default this wiki is accessible via <app_name>.wikis.orchestra.lan")
|
||||
},
|
||||
'drupal-mu': {
|
||||
'verbose_name': _("Drupdal (SaaS)"),
|
||||
'directive': ('fpm', 'fcgi://127.0.0.1:8991/home/httpd/drupal-mu/'),
|
||||
'help_text': _("This creates a Drupal site into a multi-tenant Drupal server.<br>"
|
||||
"The installation will be completed after visiting "
|
||||
"http://<app_name>.drupal.orchestra.lan/install.php?profile=standard<br>"
|
||||
"By default this site will be accessible via <app_name>.drupal.orchestra.lan")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
@ -282,3 +302,22 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
|||
r'^[^ ]+$'
|
||||
),
|
||||
})
|
||||
|
||||
|
||||
|
||||
WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD = getattr(settings, 'WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD',
|
||||
'secret')
|
||||
|
||||
WEBAPPS_WORDPRESSMU_BASE_URL = getattr(settings, 'WEBAPPS_WORDPRESSMU_BASE_URL',
|
||||
'http://blogs.orchestra.lan/')
|
||||
|
||||
|
||||
WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH = getattr(settings, 'WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH',
|
||||
'/home/httpd/htdocs/wikifarm/template.tar.gz')
|
||||
|
||||
WEBAPPS_DOKUWIKIMU_FARM_PATH = getattr(settings, 'WEBAPPS_DOKUWIKIMU_FARM_PATH',
|
||||
'/home/httpd/htdocs/wikifarm/farm')
|
||||
|
||||
|
||||
WEBAPPS_DRUPAL_SITES_PATH = getattr(settings, 'WEBAPPS_DRUPAL_SITES_PATH',
|
||||
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s')
|
||||
|
|
|
@ -50,8 +50,8 @@ class Apache2Backend(ServiceController):
|
|||
} || {
|
||||
echo -e '%(apache_conf)s' > %(sites_available)s
|
||||
UPDATED=1
|
||||
}""" % context
|
||||
))
|
||||
}""") % context
|
||||
)
|
||||
self.enable_or_disable(site)
|
||||
|
||||
def delete(self, site):
|
||||
|
@ -92,7 +92,7 @@ class Apache2Backend(ServiceController):
|
|||
Options +ExecCGI
|
||||
AddHandler fcgid-script .php
|
||||
FcgidWrapper %(fcgid_path)s\
|
||||
""" % context)
|
||||
""") % context
|
||||
for option in content.webapp.options.filter(name__startswith='Fcgid'):
|
||||
fcgid += " %s %s\n" % (option.name, option.value)
|
||||
fcgid += "</Directory>\n"
|
||||
|
@ -231,7 +231,7 @@ class Apache2Traffic(ServiceMonitor):
|
|||
END_DATE=$(date '+%%Y%%m%%d%%H%%M%%S' -d '%(current_date)s')
|
||||
LOG_FILE="$3"
|
||||
{
|
||||
{ grep "%(ignore_hosts)s" "${LOG_FILE}" || echo '\\n'; } \\
|
||||
{ grep %(ignore_hosts)s "${LOG_FILE}" || echo '\\n'; } \\
|
||||
| awk -v ini="${INI_DATE}" -v end="${END_DATE}" '
|
||||
BEGIN {
|
||||
sum = 0
|
||||
|
|
|
@ -12,6 +12,7 @@ from . import settings
|
|||
|
||||
|
||||
class Website(models.Model):
|
||||
""" Models a web site, also known as virtual host """
|
||||
name = models.CharField(_("name"), max_length=128, unique=True,
|
||||
validators=[validators.validate_name])
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||
|
|
|
@ -122,8 +122,8 @@ AUTHENTICATION_BACKENDS = [
|
|||
]
|
||||
|
||||
|
||||
#TODO Email config
|
||||
#EMAIL_BACKEND = 'djcelery_email.backends.CeleryEmailBackend'
|
||||
# Email config
|
||||
EMAIL_BACKEND = 'djcelery_email.backends.CeleryEmailBackend'
|
||||
|
||||
|
||||
#################################
|
||||
|
|
Loading…
Reference in a new issue