diff --git a/TODO.md b/TODO.md
index d6bf4848..ecbd5b3b 100644
--- a/TODO.md
+++ b/TODO.md
@@ -376,3 +376,18 @@ TODO mount the filesystem with "nosuid" option
# orchestrate async stdout stderr (inspired on pangea managemengt commands)
# orchestra-beat support for uwsgi cron
+
+# message.log if 1: return changeform
+
+# generate nginx certs on project dir rather than nginx
+
+# Register icons
+
+# send_message doesn't log task
+
+make django admin taskstate uncollapse fucking traceback, ( if exists ?)
+
+# receive tass stuck at RECEIVED
+# monitor tasks in started and backend already in success?
+
+# custom message on admin save
diff --git a/orchestra/admin/dashboard.py b/orchestra/admin/dashboard.py
index 88e30b60..2bbbc51a 100644
--- a/orchestra/admin/dashboard.py
+++ b/orchestra/admin/dashboard.py
@@ -1,45 +1,61 @@
from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
from fluent_dashboard import dashboard
from fluent_dashboard.modules import CmsAppIconList
-from orchestra.core import services
+from orchestra.core import services, accounts, administration
+
+
+class AppDefaultIconList(CmsAppIconList):
+ def __init__(self, *args, **kwargs):
+ self.icons = kwargs.pop('icons')
+ super(AppDefaultIconList, self).__init__(*args, **kwargs)
+
+ def get_icon_for_model(self, app_name, model_name, default=None):
+ icon = self.icons.get('.'.join((app_name, model_name)))
+ return super(AppDefaultIconList, self).get_icon_for_model(app_name, model_name, default=icon)
class OrchestraIndexDashboard(dashboard.FluentIndexDashboard):
- _registry = {}
-
- @classmethod
- def register_link(cls, module, view_name, title):
- registered = cls._registry.get(module, [])
- registered.append((view_name, title))
- cls._registry[module] = registered
+ def process_registered_view(self, module, view_name, options):
+ app_name, name = view_name.split('_')[:-1]
+ module.icons['.'.join((app_name, name))] = options.get('icon')
+ url = reverse('admin:' + view_name)
+ add_url = '/'.join(url.split('/')[:-2])
+ module.children.append({
+ 'models': [{
+ 'add_url': add_url,
+ 'app_name': app_name,
+ 'change_url': url,
+ 'name': name,
+ 'title': options.get('verbose_name')}],
+ 'name': app_name,
+ 'title': options.get('verbose_name'),
+ 'url': add_url,
+ })
def get_application_modules(self):
- modules = super(OrchestraIndexDashboard, self).get_application_modules()
- models = []
- for model, options in services.get().items():
- if options.get('menu', True):
- models.append("%s.%s" % (model.__module__, model._meta.object_name))
-
- for module in modules:
- registered = self._registry.get(module.title, None)
- if registered:
- for view_name, title in registered:
- # This values are shit, but it is how fluent dashboard will look for the icon
- app_name, name = view_name.split('_')[:-1]
- url = reverse('admin:' + view_name)
- add_url = '/'.join(url.split('/')[:-2])
- module.children.append({
- 'models': [{
- 'add_url': add_url,
- 'app_name': app_name,
- 'change_url': url,
- 'name': name,
- 'title': title }],
- 'name': app_name,
- 'title': title,
- 'url': add_url,
- })
- service_icon_list = CmsAppIconList('Services', models=models, collapsible=True)
- modules.append(service_icon_list)
+ from fluent_dashboard import appsettings
+ modules = []
+ # Honor settings override, hacky. I Know
+ if appsettings.FLUENT_DASHBOARD_APP_GROUPS[0][0] != _('CMS'):
+ modules = super(OrchestraIndexDashboard, self).get_application_modules()
+ for register in (accounts, administration, services):
+ title = register.verbose_name
+ models = []
+ icons = {}
+ views = []
+ for model, options in register.get().items():
+ if isinstance(model, str):
+ views.append((model, options))
+ elif options.get('dashboard', True):
+ opts = model._meta
+ label = "%s.%s" % (model.__module__, opts.object_name)
+ models.append(label)
+ label = '.'.join((opts.app_label, opts.model_name))
+ icons[label] = options.get('icon')
+ module = AppDefaultIconList(title, models=models, icons=icons, collapsible=True)
+ for view_name, options in views:
+ self.process_registered_view(module, view_name, options)
+ modules.append(module)
return modules
diff --git a/orchestra/admin/menu.py b/orchestra/admin/menu.py
index 27d3b4df..788336bd 100644
--- a/orchestra/admin/menu.py
+++ b/orchestra/admin/menu.py
@@ -27,80 +27,37 @@ def api_link(context):
return reverse('api-root')
-def process_registered_models(register):
- childrens = []
- for model, options in register.get().items():
- if options.get('menu', True):
+from copy import copy
+def process_registry(register):
+ def get_item(model, options):
+ if isinstance(model, str):
+ url = reverse('admin:'+model)
+ else:
opts = model._meta
url = reverse('admin:{}_{}_changelist'.format(
- opts.app_label, opts.model_name))
- name = capfirst(options.get('verbose_name_plural'))
- childrens.append(items.MenuItem(name, url))
- return childrens
-
-
-def get_services():
- childrens = process_registered_models(services)
- return sorted(childrens, key=lambda i: i.title)
-
-
-def get_accounts():
- childrens=[]
- if isinstalled('orchestra.contrib.payments'):
- url = reverse('admin:payments_transactionprocess_changelist')
- childrens.append(items.MenuItem(_("Transaction processes"), url))
- if isinstalled('orchestra.contrib.issues'):
- url = reverse('admin:issues_ticket_changelist')
- childrens.append(items.MenuItem(_("Tickets"), url))
- childrens.extend(process_registered_models(accounts))
- return sorted(childrens, key=lambda i: i.title)
-
-
-def get_administration_items():
- childrens = []
- if isinstalled('orchestra.contrib.settings'):
- url = reverse('admin:settings_setting_change')
- childrens.append(items.MenuItem(_("Settings"), url))
- if isinstalled('orchestra.contrib.services'):
- url = reverse('admin:services_service_changelist')
- childrens.append(items.MenuItem(_("Services"), url))
- url = reverse('admin:plans_plan_changelist')
- childrens.append(items.MenuItem(_("Plans"), url))
- if isinstalled('orchestra.contrib.orchestration'):
- route = reverse('admin:orchestration_route_changelist')
- backendlog = reverse('admin:orchestration_backendlog_changelist')
- server = reverse('admin:orchestration_server_changelist')
- childrens.append(items.MenuItem(_("Orchestration"), route, children=[
- items.MenuItem(_("Routes"), route),
- items.MenuItem(_("Backend logs"), backendlog),
- items.MenuItem(_("Servers"), server),
- ]))
- if isinstalled('orchestra.contrib.resources'):
- resource = reverse('admin:resources_resource_changelist')
- data = reverse('admin:resources_resourcedata_changelist')
- monitor = reverse('admin:resources_monitordata_changelist')
- childrens.append(items.MenuItem(_("Resources"), resource, children=[
- items.MenuItem(_("Resources"), resource),
- items.MenuItem(_("Data"), data),
- items.MenuItem(_("Monitoring"), monitor),
- ]))
- if isinstalled('orchestra.contrib.miscellaneous'):
- url = reverse('admin:miscellaneous_miscservice_changelist')
- childrens.append(items.MenuItem(_("Miscellaneous"), url))
- if isinstalled('orchestra.contrib.issues'):
- url = reverse('admin:issues_queue_changelist')
- childrens.append(items.MenuItem(_("Ticket queues"), url))
- if isinstalled('djcelery'):
- task = reverse('admin:djcelery_taskstate_changelist')
- periodic = reverse('admin:djcelery_periodictask_changelist')
- worker = reverse('admin:djcelery_workerstate_changelist')
- childrens.append(items.MenuItem(_("Tasks"), task, children=[
- items.MenuItem(_("Logs"), task),
- items.MenuItem(_("Periodic tasks"), periodic),
- items.MenuItem(_("Workers"), worker),
- ]))
- childrens.extend(process_registered_models(administration))
- return childrens
+ opts.app_label, opts.model_name))
+ name = capfirst(options.get('verbose_name_plural'))
+ return items.MenuItem(name, url)
+
+ childrens = {}
+ for model, options in register.get().items():
+ if options.get('menu', True):
+ parent = options.get('parent')
+ if parent:
+ parent_item = childrens.get(parent)
+ if parent_item:
+ if not parent_item.children:
+ parent_item.children.append(copy(parent_item))
+ else:
+ parent_item = get_item(parent, register[parent])
+ parent_item.children = []
+ parent_item.children.append(get_item(model, options))
+ childrens[parent] = parent_item
+ elif model not in childrens:
+ childrens[model] = get_item(model, options)
+ else:
+ childrens[model].children.insert(0, get_item(model, options))
+ return sorted(childrens.values(), key=lambda i: i.title)
class OrchestraMenu(Menu):
@@ -122,16 +79,16 @@ class OrchestraMenu(Menu):
# items.Bookmarks(),
items.MenuItem(
_("Services"),
- children=get_services()
+ children=process_registry(services)
),
items.MenuItem(
_("Accounts"),
reverse('admin:accounts_account_changelist'),
- children=get_accounts()
+ children=process_registry(accounts)
),
items.MenuItem(
_("Administration"),
- children=get_administration_items()
+ children=process_registry(administration)
),
items.MenuItem("API", api_link(context)),
]
diff --git a/orchestra/conf/project_template/project_name/settings.py b/orchestra/conf/project_template/project_name/settings.py
index 81462018..6c940c63 100644
--- a/orchestra/conf/project_template/project_name/settings.py
+++ b/orchestra/conf/project_template/project_name/settings.py
@@ -233,75 +233,6 @@ ADMIN_TOOLS_MENU = 'orchestra.admin.menu.OrchestraMenu'
# Fluent dashboard
ADMIN_TOOLS_INDEX_DASHBOARD = 'orchestra.admin.dashboard.OrchestraIndexDashboard'
FLUENT_DASHBOARD_ICON_THEME = '../orchestra/icons'
-FLUENT_DASHBOARD_APP_GROUPS = (
- # Services group is generated by orchestra.admin.dashboard
- ('Accounts', {
- 'models': (
- 'orchestra.contrib.accounts.models.Account',
- 'orchestra.contrib.contacts.models.Contact',
- 'orchestra.contrib.orders.models.Order',
- 'orchestra.contrib.plans.models.ContractedPlan',
- 'orchestra.contrib.bills.models.Bill',
- 'orchestra.contrib.payments.models.Transaction',
- 'orchestra.contrib.issues.models.Ticket',
- ),
- 'collapsible': True,
- }),
- ('Administration', {
- 'models': (
- 'djcelery.models.TaskState',
- 'orchestra.contrib.orchestration.models.Route',
- 'orchestra.contrib.orchestration.models.BackendLog',
- 'orchestra.contrib.orchestration.models.Server',
- 'orchestra.contrib.resources.models.Resource',
- 'orchestra.contrib.resources.models.ResourceData',
- 'orchestra.contrib.services.models.Service',
- 'orchestra.contrib.plans.models.Plan',
- 'orchestra.contrib.miscellaneous.models.MiscService',
- ),
- 'collapsible': True,
- }),
-)
-
-FLUENT_DASHBOARD_APP_ICONS = {
- # Services
- 'webs/web': 'web.png',
- 'mail/address': 'X-office-address-book.png',
- 'mailboxes/mailbox': 'email.png',
- 'mailboxes/address': 'X-office-address-book.png',
- 'lists/list': 'email-alter.png',
- 'domains/domain': 'domain.png',
- 'multitenance/tenant': 'apps.png',
- 'webapps/webapp': 'Applications-other.png',
- 'websites/website': 'Applications-internet.png',
- 'databases/database': 'database.png',
- 'databases/databaseuser': 'postgresql.png',
- 'vps/vps': 'TuxBox.png',
- 'miscellaneous/miscellaneous': 'applications-other.png',
- 'saas/saas': 'saas.png',
- 'systemusers/systemuser': 'roleplaying.png',
- # Accounts
- 'accounts/account': 'Face-monkey.png',
- 'contacts/contact': 'contact_book.png',
- 'orders/order': 'basket.png',
- 'plans/contractedplan': 'ContractedPack.png',
- 'services/service': 'price.png',
- 'bills/bill': 'invoice.png',
- 'payments/paymentsource': 'card_in_use.png',
- 'payments/transaction': 'transaction.png',
- 'payments/transactionprocess': 'transactionprocess.png',
- 'issues/ticket': 'Ticket_star.png',
- 'miscellaneous/miscservice': 'Misc-Misc-Box-icon.png',
- # Administration
- 'settings/setting': 'preferences.png',
- 'djcelery/taskstate': 'taskstate.png',
- 'orchestration/server': 'vps.png',
- 'orchestration/route': 'hal.png',
- 'orchestration/backendlog': 'scriptlog.png',
- 'resources/resource': "gauge.png",
- 'resources/resourcedata': "monitor.png",
- 'plans/plan': 'Pack.png',
-}
# Django-celery
diff --git a/orchestra/contrib/accounts/apps.py b/orchestra/contrib/accounts/apps.py
index e3d54378..81e73667 100644
--- a/orchestra/contrib/accounts/apps.py
+++ b/orchestra/contrib/accounts/apps.py
@@ -12,7 +12,7 @@ class AccountConfig(AppConfig):
def ready(self):
from .management import create_initial_superuser
from .models import Account
- services.register(Account, menu=False)
- accounts.register(Account)
+ services.register(Account, menu=False, dashboard=False)
+ accounts.register(Account, icon='Face-monkey.png')
post_migrate.connect(create_initial_superuser,
dispatch_uid="orchestra.contrib.accounts.management.createsuperuser")
diff --git a/orchestra/contrib/bills/__init__.py b/orchestra/contrib/bills/__init__.py
index e69de29b..c568ce60 100644
--- a/orchestra/contrib/bills/__init__.py
+++ b/orchestra/contrib/bills/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.bills.apps.BillsConfig'
diff --git a/orchestra/contrib/bills/apps.py b/orchestra/contrib/bills/apps.py
new file mode 100644
index 00000000..ecc74588
--- /dev/null
+++ b/orchestra/contrib/bills/apps.py
@@ -0,0 +1,12 @@
+from django.apps import AppConfig
+
+from orchestra.core import accounts
+
+
+class BillsConfig(AppConfig):
+ name = 'orchestra.contrib.bills'
+ verbose_name = 'Bills'
+
+ def ready(self):
+ from .models import Bill
+ accounts.register(Bill, icon='invoice.png')
diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py
index 9ab06ee0..6e924e8d 100644
--- a/orchestra/contrib/bills/models.py
+++ b/orchestra/contrib/bills/models.py
@@ -13,7 +13,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.accounts.models import Account
from orchestra.contrib.contacts.models import Contact
-from orchestra.core import accounts, validators
+from orchestra.core import validators
from orchestra.utils.html import html_to_pdf
from . import settings
@@ -351,6 +351,3 @@ class BillSubline(models.Model):
# if self.line.bill.is_open:
# self.line.bill.total = self.line.bill.get_total()
# self.line.bill.save(update_fields=['total'])
-
-
-accounts.register(Bill)
diff --git a/orchestra/contrib/contacts/__init__.py b/orchestra/contrib/contacts/__init__.py
index e69de29b..3af15748 100644
--- a/orchestra/contrib/contacts/__init__.py
+++ b/orchestra/contrib/contacts/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.contacts.apps.ContactsConfig'
diff --git a/orchestra/contrib/contacts/apps.py b/orchestra/contrib/contacts/apps.py
new file mode 100644
index 00000000..4ed7fe70
--- /dev/null
+++ b/orchestra/contrib/contacts/apps.py
@@ -0,0 +1,12 @@
+from django.apps import AppConfig
+
+from orchestra.core import accounts
+
+
+class ContactsConfig(AppConfig):
+ name = 'orchestra.contrib.contacts'
+ verbose_name = 'Contacts'
+
+ def ready(self):
+ from .models import Contact
+ accounts.register(Contact, icon='contact_book.png')
diff --git a/orchestra/contrib/contacts/models.py b/orchestra/contrib/contacts/models.py
index e981b761..42fee3fe 100644
--- a/orchestra/contrib/contacts/models.py
+++ b/orchestra/contrib/contacts/models.py
@@ -3,7 +3,7 @@ from django.core.validators import RegexValidator
from django.db import models
from django.utils.translation import ugettext_lazy as _
-from orchestra.core import accounts, validators
+from orchestra.core import validators
from orchestra.models.fields import MultiSelectField
from . import settings
@@ -78,6 +78,3 @@ class Contact(models.Model):
errors['zipcode'] = error
if errors:
raise ValidationError(errors)
-
-
-accounts.register(Contact)
diff --git a/orchestra/contrib/databases/__init__.py b/orchestra/contrib/databases/__init__.py
index e69de29b..f21f8dda 100644
--- a/orchestra/contrib/databases/__init__.py
+++ b/orchestra/contrib/databases/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.databases.apps.DatabasesConfig'
diff --git a/orchestra/contrib/databases/apps.py b/orchestra/contrib/databases/apps.py
new file mode 100644
index 00000000..97f8ef4a
--- /dev/null
+++ b/orchestra/contrib/databases/apps.py
@@ -0,0 +1,14 @@
+from django.apps import AppConfig
+from django.utils.translation import ugettext_lazy as _
+
+from orchestra.core import services
+
+
+class DatabasesConfig(AppConfig):
+ name = 'orchestra.contrib.databases'
+ verbose_name = 'Databases'
+
+ def ready(self):
+ from .models import Database, DatabaseUser
+ services.register(Database, icon='database.png')
+ services.register(DatabaseUser, icon='postgresql.png', verbose_name_plural=_("Database users"))
diff --git a/orchestra/contrib/databases/models.py b/orchestra/contrib/databases/models.py
index b04e1ff1..2270a1f8 100644
--- a/orchestra/contrib/databases/models.py
+++ b/orchestra/contrib/databases/models.py
@@ -3,7 +3,7 @@ import hashlib
from django.db import models
from django.utils.translation import ugettext_lazy as _
-from orchestra.core import validators, services
+from orchestra.core import validators
from . import settings
@@ -76,7 +76,3 @@ class DatabaseUser(models.Model):
self.password = '*%s' % hexdigest.upper()
else:
raise TypeError("Database type '%s' not supported" % self.type)
-
-
-services.register(Database)
-services.register(DatabaseUser, verbose_name_plural=_("Database users"))
diff --git a/orchestra/contrib/domains/__init__.py b/orchestra/contrib/domains/__init__.py
index e69de29b..5c85353e 100644
--- a/orchestra/contrib/domains/__init__.py
+++ b/orchestra/contrib/domains/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.domains.apps.DomainsConfig'
diff --git a/orchestra/contrib/domains/admin.py b/orchestra/contrib/domains/admin.py
index 78ff706f..1d7e7ce5 100644
--- a/orchestra/contrib/domains/admin.py
+++ b/orchestra/contrib/domains/admin.py
@@ -11,8 +11,8 @@ from orchestra.contrib.accounts.admin import AccountAdminMixin
from orchestra.utils import apps
from .actions import view_zone
-from .forms import RecordInlineFormSet, BatchDomainCreationAdminForm
from .filters import TopDomainListFilter
+from .forms import RecordInlineFormSet, BatchDomainCreationAdminForm
from .models import Domain, Record
@@ -21,11 +21,6 @@ class RecordInline(admin.TabularInline):
formset = RecordInlineFormSet
verbose_name_plural = _("Extra records")
-# class Media:
-# css = {
-# 'all': ('orchestra/css/hide-inline-id.css',)
-# }
-#
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'value':
@@ -73,9 +68,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
change_view_actions = [view_zone]
def structured_name(self, domain):
- if not domain.is_top:
- return ' '*4 + domain.name
- return domain.name
+ if domain.is_top:
+ return domain.name
+ return ' '*4 + domain.name
structured_name.short_description = _("name")
structured_name.allow_tags = True
structured_name.admin_order_field = 'structured_name'
diff --git a/orchestra/contrib/domains/apps.py b/orchestra/contrib/domains/apps.py
new file mode 100644
index 00000000..559166c4
--- /dev/null
+++ b/orchestra/contrib/domains/apps.py
@@ -0,0 +1,12 @@
+from django.apps import AppConfig
+
+from orchestra.core import services
+
+
+class DomainsConfig(AppConfig):
+ name = 'orchestra.contrib.domains'
+ verbose_name = 'Domains'
+
+ def ready(self):
+ from .models import Domain
+ services.register(Domain, icon='domain.png')
diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py
index 5b44e476..f73743bb 100644
--- a/orchestra/contrib/domains/models.py
+++ b/orchestra/contrib/domains/models.py
@@ -2,7 +2,6 @@ from django.core.exceptions import ValidationError
from django.db import models
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
from orchestra.utils.python import AttrDict
@@ -271,6 +270,3 @@ class Record(models.Model):
def get_ttl(self):
return self.ttl or settings.DOMAINS_DEFAULT_TTL
-
-
-services.register(Domain)
diff --git a/orchestra/contrib/issues/__init__.py b/orchestra/contrib/issues/__init__.py
index edea65c4..650ba7fe 100644
--- a/orchestra/contrib/issues/__init__.py
+++ b/orchestra/contrib/issues/__init__.py
@@ -1 +1 @@
-REQUIRED_APPS = ['slices']
+default_app_config = 'orchestra.contrib.issues.apps.IssuesConfig'
diff --git a/orchestra/contrib/issues/apps.py b/orchestra/contrib/issues/apps.py
new file mode 100644
index 00000000..fdc35f24
--- /dev/null
+++ b/orchestra/contrib/issues/apps.py
@@ -0,0 +1,13 @@
+from django.apps import AppConfig
+
+from orchestra.core import accounts, administration
+
+
+class IssuesConfig(AppConfig):
+ name = 'orchestra.contrib.issues'
+ verbose_name = "Issues"
+
+ def ready(self):
+ from .models import Queue, Ticket
+ accounts.register(Ticket, icon='Ticket_star.png')
+ administration.register(Queue, dashboard=False)
diff --git a/orchestra/contrib/lists/__init__.py b/orchestra/contrib/lists/__init__.py
index e69de29b..413f2e03 100644
--- a/orchestra/contrib/lists/__init__.py
+++ b/orchestra/contrib/lists/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.lists.apps.ListsConfig'
diff --git a/orchestra/contrib/lists/apps.py b/orchestra/contrib/lists/apps.py
new file mode 100644
index 00000000..7e6e772c
--- /dev/null
+++ b/orchestra/contrib/lists/apps.py
@@ -0,0 +1,12 @@
+from django.apps import AppConfig
+
+from orchestra.core import services
+
+
+class ListsConfig(AppConfig):
+ name = 'orchestra.contrib.lists'
+ verbose_name = 'Lists'
+
+ def ready(self):
+ from .models import List
+ services.register(List, icon='email-alter.png')
diff --git a/orchestra/contrib/lists/models.py b/orchestra/contrib/lists/models.py
index 8d1ba5ed..cb9caefa 100644
--- a/orchestra/contrib/lists/models.py
+++ b/orchestra/contrib/lists/models.py
@@ -2,7 +2,6 @@ from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
-from orchestra.core import services
from orchestra.core.validators import validate_name
from . import settings
@@ -70,6 +69,3 @@ class List(models.Model):
'name': self.name
}
return settings.LISTS_LIST_URL % context
-
-
-services.register(List)
diff --git a/orchestra/contrib/mailboxes/__init__.py b/orchestra/contrib/mailboxes/__init__.py
index e69de29b..dbf89749 100644
--- a/orchestra/contrib/mailboxes/__init__.py
+++ b/orchestra/contrib/mailboxes/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.mailboxes.apps.MailboxesConfig'
diff --git a/orchestra/contrib/mailboxes/apps.py b/orchestra/contrib/mailboxes/apps.py
new file mode 100644
index 00000000..9171c4ea
--- /dev/null
+++ b/orchestra/contrib/mailboxes/apps.py
@@ -0,0 +1,13 @@
+from django.apps import AppConfig
+
+from orchestra.core import services
+
+
+class MailboxesConfig(AppConfig):
+ name = 'orchestra.contrib.mailboxes'
+ verbose_name = 'Mailboxes'
+
+ def ready(self):
+ from .models import Mailbox, Address
+ services.register(Mailbox, icon='email.png')
+ services.register(Address, icon='X-office-address-book.png')
diff --git a/orchestra/contrib/mailboxes/models.py b/orchestra/contrib/mailboxes/models.py
index 7a3811f6..8f4cad4b 100644
--- a/orchestra/contrib/mailboxes/models.py
+++ b/orchestra/contrib/mailboxes/models.py
@@ -6,8 +6,6 @@ from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
-from orchestra.core import services
-
from . import validators, settings
@@ -152,7 +150,3 @@ class Autoresponse(models.Model):
def __str__(self):
return self.address
-
-
-services.register(Mailbox)
-services.register(Address)
diff --git a/orchestra/contrib/mailer/apps.py b/orchestra/contrib/mailer/apps.py
index fd8cd958..c680cef8 100644
--- a/orchestra/contrib/mailer/apps.py
+++ b/orchestra/contrib/mailer/apps.py
@@ -9,4 +9,4 @@ class MailerConfig(AppConfig):
def ready(self):
from .models import Message
- administration.register(Message)
+ administration.register(Message, icon='Mail-send.png')
diff --git a/orchestra/contrib/mailer/models.py b/orchestra/contrib/mailer/models.py
index 6a943f47..21786c1e 100644
--- a/orchestra/contrib/mailer/models.py
+++ b/orchestra/contrib/mailer/models.py
@@ -43,7 +43,7 @@ class Message(models.Model):
# Max tries
if self.retries >= len(settings.MAILER_DEFERE_SECONDS):
self.state = self.FAILED
- self.save(update_fields=('state', 'retries'))
+ self.save(update_fields=('state', 'retries', 'last_retry'))
def sent(self):
self.state = self.SENT
diff --git a/orchestra/contrib/miscellaneous/__init__.py b/orchestra/contrib/miscellaneous/__init__.py
index e69de29b..6294909f 100644
--- a/orchestra/contrib/miscellaneous/__init__.py
+++ b/orchestra/contrib/miscellaneous/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.miscellaneous.apps.MiscellaneousConfig'
diff --git a/orchestra/contrib/miscellaneous/apps.py b/orchestra/contrib/miscellaneous/apps.py
new file mode 100644
index 00000000..2f5763ae
--- /dev/null
+++ b/orchestra/contrib/miscellaneous/apps.py
@@ -0,0 +1,15 @@
+from django.apps import AppConfig
+
+from orchestra.core import services, administration
+from orchestra.core.translations import ModelTranslation
+
+
+class MiscellaneousConfig(AppConfig):
+ name = 'orchestra.contrib.miscellaneous'
+ verbose_name = 'Miscellaneous'
+
+ def ready(self):
+ from .models import MiscService, Miscellaneous
+ services.register(Miscellaneous, icon='applications-other.png')
+ administration.register(MiscService, icon='Misc-Misc-Box-icon.png')
+ ModelTranslation.register(MiscService, ('verbose_name',))
diff --git a/orchestra/contrib/miscellaneous/models.py b/orchestra/contrib/miscellaneous/models.py
index 663b4232..f3233133 100644
--- a/orchestra/contrib/miscellaneous/models.py
+++ b/orchestra/contrib/miscellaneous/models.py
@@ -2,8 +2,6 @@ from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
-from orchestra.core import services
-from orchestra.core.translations import ModelTranslation
from orchestra.core.validators import validate_name
from orchestra.models.fields import NullableCharField
@@ -69,8 +67,3 @@ class Miscellaneous(models.Model):
if self.identifier:
self.identifier = self.identifier.strip()
self.description = self.description.strip()
-
-
-services.register(Miscellaneous)
-
-ModelTranslation.register(MiscService, ('verbose_name',))
diff --git a/orchestra/contrib/orchestration/__init__.py b/orchestra/contrib/orchestration/__init__.py
index 89d0113b..262c9d81 100644
--- a/orchestra/contrib/orchestration/__init__.py
+++ b/orchestra/contrib/orchestration/__init__.py
@@ -3,6 +3,9 @@ import copy
from .backends import ServiceBackend, ServiceController, replace
+default_app_config = 'orchestra.contrib.orchestration.apps.OrchestrationConfig'
+
+
class Operation():
DELETE = 'delete'
SAVE = 'save'
@@ -30,10 +33,10 @@ class Operation():
self.routes = routes
@classmethod
- def execute(cls, operations, async=False):
+ def execute(cls, operations, serialize=False, async=False):
from . import manager
- scripts, block = manager.generate(operations)
- return manager.execute(scripts, block=block, async=async)
+ scripts, oserialize = manager.generate(operations)
+ return manager.execute(scripts, serialize=(serialize or oserialize), async=async)
@classmethod
def execute_action(cls, instance, action):
diff --git a/orchestra/contrib/orchestration/apps.py b/orchestra/contrib/orchestration/apps.py
new file mode 100644
index 00000000..6de145dd
--- /dev/null
+++ b/orchestra/contrib/orchestration/apps.py
@@ -0,0 +1,14 @@
+from django.apps import AppConfig
+
+from orchestra.core import administration
+
+
+class OrchestrationConfig(AppConfig):
+ name = 'orchestra.contrib.orchestration'
+ verbose_name = "Orchestration"
+
+ def ready(self):
+ from .models import Server, Route, BackendLog
+ administration.register(BackendLog, icon='scriptlog.png')
+ administration.register(Server, parent=BackendLog, icon='vps.png')
+ administration.register(Route, parent=BackendLog, icon='hal.png')
diff --git a/orchestra/contrib/orchestration/backends.py b/orchestra/contrib/orchestration/backends.py
index d32a2bdf..cd52aa79 100644
--- a/orchestra/contrib/orchestration/backends.py
+++ b/orchestra/contrib/orchestration/backends.py
@@ -45,7 +45,7 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount):
actions = []
default_route_match = 'True'
# Force the backend manager to block in multiple backend executions executing them synchronously
- block = False
+ serialize = False
doc_settings = None
# By default backend will not run if actions do not generate insctructions,
# If your backend uses prepare() or commit() only then you should set force_empty_action_execution = True
diff --git a/orchestra/contrib/orchestration/management/commands/orchestrate.py b/orchestra/contrib/orchestration/management/commands/orchestrate.py
index 69fc11fc..defeb4c4 100644
--- a/orchestra/contrib/orchestration/management/commands/orchestrate.py
+++ b/orchestra/contrib/orchestration/management/commands/orchestrate.py
@@ -71,7 +71,7 @@ class Command(BaseCommand):
else:
for instance in queryset:
manager.collect(instance, action, operations=operations, route_cache=route_cache)
- scripts, block = manager.generate(operations)
+ scripts, serialize = manager.generate(operations)
servers = []
# Print scripts
for key, value in scripts.items():
@@ -96,7 +96,7 @@ class Command(BaseCommand):
return
break
if not dry:
- logs = manager.execute(scripts, block=block)
+ logs = manager.execute(scripts, serialize=serialize)
for log in logs:
self.stdout.write(log.stdout)
self.stderr.write(log.stderr)
diff --git a/orchestra/contrib/orchestration/manager.py b/orchestra/contrib/orchestration/manager.py
index 9cc86d70..0ee544f4 100644
--- a/orchestra/contrib/orchestration/manager.py
+++ b/orchestra/contrib/orchestration/manager.py
@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
router = import_class(settings.ORCHESTRATION_ROUTER)
-def as_task(execute, log, operations):
+def keep_log(execute, log, operations):
def wrapper(*args, **kwargs):
""" send report """
# Remember that threads have their oun connection poll
@@ -30,14 +30,14 @@ def as_task(execute, log, operations):
if log.state != log.SUCCESS:
send_report(execute, args, log)
except Exception as e:
- subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs))
- message = traceback.format_exc()
- logger.error(subject)
- logger.error(message)
- mail_admins(subject, message)
+ trace = traceback.format_exc()
log.state = BackendLog.EXCEPTION
- log.stderr = traceback.format_exc()
- log.save(update_fields=('state', 'stderr'))
+ log.stderr = trace
+ log.save()
+ subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs))
+ logger.error(subject)
+ logger.error(trace)
+ mail_admins(subject, trace)
# We don't propagate the exception further to avoid transaction rollback
finally:
# Store and log the operation
@@ -56,7 +56,7 @@ def as_task(execute, log, operations):
def generate(operations):
scripts = OrderedDict()
cache = {}
- block = False
+ serialize = False
# Generate scripts per route+backend
for operation in operations:
logger.debug("Queued %s" % str(operation))
@@ -86,18 +86,18 @@ def generate(operations):
pre_action.send(**kwargs)
method(operation.instance)
post_action.send(**kwargs)
- if backend.block:
- block = True
+ if backend.serialize:
+ serialize = True
for value in scripts.values():
backend, operations = value
backend.set_tail()
pre_commit.send(sender=backend.__class__, backend=backend)
backend.commit()
post_commit.send(sender=backend.__class__, backend=backend)
- return scripts, block
+ return scripts, serialize
-def execute(scripts, block=False, async=False):
+def execute(scripts, serialize=False, async=False):
""" executes the operations on the servers """
if settings.ORCHESTRATION_DISABLE_EXECUTION:
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION settings.')
@@ -110,21 +110,22 @@ def execute(scripts, block=False, async=False):
route, __ = key
backend, operations = value
args = (route.host,)
+ async = not serialize and (async or route.async)
kwargs = {
- 'async': async or route.async
+ 'async': async,
}
log = backend.create_log(*args, **kwargs)
kwargs['log'] = log
- task = as_task(backend.execute, log, operations)
+ task = keep_log(backend.execute, log, operations)
logger.debug('%s is going to be executed on %s' % (backend, route.host))
- if block:
+ if serialize:
# Execute one backend at a time, no need for threads
task(*args, **kwargs)
else:
task = close_connection(task)
thread = threading.Thread(target=task, args=args, kwargs=kwargs)
thread.start()
- if not route.async:
+ if not async:
threads_to_join.append(thread)
logs.append(log)
[ thread.join() for thread in threads_to_join ]
diff --git a/orchestra/contrib/orchestration/middlewares.py b/orchestra/contrib/orchestration/middlewares.py
index 95d68725..94fa73d3 100644
--- a/orchestra/contrib/orchestration/middlewares.py
+++ b/orchestra/contrib/orchestration/middlewares.py
@@ -97,14 +97,14 @@ class OperationsMiddleware(object):
operations = self.get_pending_operations()
if operations:
try:
- scripts, block = manager.generate(operations)
+ scripts, serialize = manager.generate(operations)
except Exception as exception:
self.leave_transaction_management(exception)
raise
# We commit transaction just before executing operations
# because here is when IntegrityError show up
self.leave_transaction_management()
- logs = manager.execute(scripts, block=block)
+ logs = manager.execute(scripts, serialize=serialize)
if logs and resolve(request.path).app_name == 'admin':
message_user(request, logs)
return response
diff --git a/orchestra/contrib/orders/apps.py b/orchestra/contrib/orders/apps.py
index 733d281b..ae588789 100644
--- a/orchestra/contrib/orders/apps.py
+++ b/orchestra/contrib/orders/apps.py
@@ -10,6 +10,6 @@ class OrdersConfig(AppConfig):
def ready(self):
from .models import Order
- accounts.register(Order)
+ accounts.register(Order, icon='basket.png')
if database_ready():
from . import signals
diff --git a/orchestra/contrib/payments/__init__.py b/orchestra/contrib/payments/__init__.py
index e69de29b..970bd432 100644
--- a/orchestra/contrib/payments/__init__.py
+++ b/orchestra/contrib/payments/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.payments.apps.PaymentsConfig'
diff --git a/orchestra/contrib/payments/apps.py b/orchestra/contrib/payments/apps.py
new file mode 100644
index 00000000..7ae1bae4
--- /dev/null
+++ b/orchestra/contrib/payments/apps.py
@@ -0,0 +1,14 @@
+from django.apps import AppConfig
+
+from orchestra.core import accounts
+
+
+class PaymentsConfig(AppConfig):
+ name = 'orchestra.contrib.payments'
+ verbose_name = "Payments"
+
+ def ready(self):
+ from .models import PaymentSource, Transaction, TransactionProcess
+ accounts.register(PaymentSource, dashboard=False)
+ accounts.register(Transaction, icon='transaction.png')
+ accounts.register(TransactionProcess, icon='transactionprocess.png', dashboard=False)
diff --git a/orchestra/contrib/payments/models.py b/orchestra/contrib/payments/models.py
index 520d681f..79ce714a 100644
--- a/orchestra/contrib/payments/models.py
+++ b/orchestra/contrib/payments/models.py
@@ -4,7 +4,6 @@ from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
-from orchestra.core import accounts
from orchestra.models.queryset import group_by
from . import settings
@@ -200,7 +199,3 @@ class TransactionProcess(models.Model):
for transaction in self.transactions.processing():
transaction.mark_as_secured()
self.save(update_fields=['state'])
-
-
-accounts.register(PaymentSource)
-accounts.register(Transaction)
diff --git a/orchestra/contrib/plans/__init__.py b/orchestra/contrib/plans/__init__.py
index e69de29b..e09642bf 100644
--- a/orchestra/contrib/plans/__init__.py
+++ b/orchestra/contrib/plans/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.plans.apps.PlansConfig'
diff --git a/orchestra/contrib/plans/apps.py b/orchestra/contrib/plans/apps.py
new file mode 100644
index 00000000..153501c9
--- /dev/null
+++ b/orchestra/contrib/plans/apps.py
@@ -0,0 +1,15 @@
+from django.apps import AppConfig
+
+from orchestra.core import administration, accounts
+from orchestra.core.translations import ModelTranslation
+
+
+class PlansConfig(AppConfig):
+ name = 'orchestra.contrib.plans'
+ verbose_name = 'Plans'
+
+ def ready(self):
+ from .models import Plan, ContractedPlan
+ accounts.register(ContractedPlan, icon='ContractedPack.png')
+ administration.register(Plan, icon='Pack.png')
+ ModelTranslation.register(Plan, ('verbose_name',))
diff --git a/orchestra/contrib/plans/models.py b/orchestra/contrib/plans/models.py
index 5a9fae38..7caf1a57 100644
--- a/orchestra/contrib/plans/models.py
+++ b/orchestra/contrib/plans/models.py
@@ -5,7 +5,6 @@ from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services, accounts
-from orchestra.core.translations import ModelTranslation
from orchestra.core.validators import validate_name
from orchestra.models import queryset
@@ -100,9 +99,3 @@ class Rate(models.Model):
for name, method in cls.RATE_METHODS.items():
choices.append((name, method.verbose_name))
return choices
-
-
-accounts.register(ContractedPlan)
-services.register(ContractedPlan, menu=False)
-
-ModelTranslation.register(Plan, ('verbose_name',))
diff --git a/orchestra/contrib/resources/apps.py b/orchestra/contrib/resources/apps.py
index ca3c4215..2cfbde65 100644
--- a/orchestra/contrib/resources/apps.py
+++ b/orchestra/contrib/resources/apps.py
@@ -1,6 +1,7 @@
from django import db
from django.apps import AppConfig
+from orchestra.core import administration
from orchestra.utils.db import database_ready
@@ -16,6 +17,10 @@ class ResourcesConfig(AppConfig):
except db.utils.OperationalError:
# Not ready afterall
pass
+ from .models import Resource, ResourceData, MonitorData
+ administration.register(Resource, icon='gauge.png')
+ administration.register(ResourceData, parent=Resource, icon='monitor.png')
+ administration.register(MonitorData, parent=Resource, dashboard=False)
def reload_relations(self):
from .admin import insert_resource_inlines
diff --git a/orchestra/contrib/resources/backends.py b/orchestra/contrib/resources/backends.py
index e5e7103e..41dd3f39 100644
--- a/orchestra/contrib/resources/backends.py
+++ b/orchestra/contrib/resources/backends.py
@@ -72,7 +72,7 @@ class ServiceMonitor(ServiceBackend):
MonitorData.objects.create(monitor=name, object_id=object_id,
content_type=ct, value=value, created_at=self.current_date)
- def execute(self, server, async=False):
- log = super(ServiceMonitor, self).execute(server, async=async)
+ def execute(self, *args, **kwargs):
+ log = super(ServiceMonitor, self).execute(*args, **kwargs)
self.store(log)
return log
diff --git a/orchestra/contrib/resources/migrations/0002_auto_20150502_1429.py b/orchestra/contrib/resources/migrations/0002_auto_20150502_1429.py
new file mode 100644
index 00000000..83c9b197
--- /dev/null
+++ b/orchestra/contrib/resources/migrations/0002_auto_20150502_1429.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resources', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CrontabSchedule',
+ fields=[
+ ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')),
+ ('minute', models.CharField(max_length=64, verbose_name='minute', default='*')),
+ ('hour', models.CharField(max_length=64, verbose_name='hour', default='*')),
+ ('day_of_week', models.CharField(max_length=64, verbose_name='day of week', default='*')),
+ ('day_of_month', models.CharField(max_length=64, verbose_name='day of month', default='*')),
+ ('month_of_year', models.CharField(max_length=64, verbose_name='month of year', default='*')),
+ ],
+ options={
+ 'verbose_name': 'crontab',
+ 'ordering': ('month_of_year', 'day_of_month', 'day_of_week', 'hour', 'minute'),
+ 'verbose_name_plural': 'crontabs',
+ },
+ ),
+ ]
diff --git a/orchestra/contrib/resources/migrations/0003_auto_20150502_1433.py b/orchestra/contrib/resources/migrations/0003_auto_20150502_1433.py
new file mode 100644
index 00000000..7a3f3472
--- /dev/null
+++ b/orchestra/contrib/resources/migrations/0003_auto_20150502_1433.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resources', '0002_auto_20150502_1429'),
+ ]
+
+ operations = [
+ ]
diff --git a/orchestra/contrib/resources/migrations/0004_auto_20150503_1559.py b/orchestra/contrib/resources/migrations/0004_auto_20150503_1559.py
new file mode 100644
index 00000000..1b717fe6
--- /dev/null
+++ b/orchestra/contrib/resources/migrations/0004_auto_20150503_1559.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resources', '0003_auto_20150502_1433'),
+ ]
+
+ operations = [
+ ]
diff --git a/orchestra/contrib/resources/models.py b/orchestra/contrib/resources/models.py
index df9ceb29..53a1c7ac 100644
--- a/orchestra/contrib/resources/models.py
+++ b/orchestra/contrib/resources/models.py
@@ -148,9 +148,8 @@ class Resource(models.Model):
def monitor(self, async=True):
if async:
- print(tasks.monitor.delay)
- return tasks.monitor.delay(self.pk, async=async)
- return tasks.monitor(self.pk, async=async)
+ return tasks.monitor.apply_async(self.pk)
+ return tasks.monitor(self.pk)
class ResourceData(models.Model):
diff --git a/orchestra/contrib/resources/tasks.py b/orchestra/contrib/resources/tasks.py
index ee983fa3..e440a36c 100644
--- a/orchestra/contrib/resources/tasks.py
+++ b/orchestra/contrib/resources/tasks.py
@@ -7,7 +7,7 @@ from .backends import ServiceMonitor
@task(name='resources.Monitor')
-def monitor(resource_id, ids=None, async=True):
+def monitor(resource_id, ids=None):
with LockFile('/dev/shm/resources.monitor-%i.lock' % resource_id, expire=60*60):
from .models import ResourceData, Resource
resource = Resource.objects.get(pk=resource_id)
@@ -29,9 +29,7 @@ def monitor(resource_id, ids=None, async=True):
for obj in model.objects.filter(**kwargs):
op = Operation(backend, obj, Operation.MONITOR)
monitorings.append(op)
- # TODO async=True only when running with celery
- # monitor.request.id
- logs += Operation.execute(monitorings, async=async)
+ logs += Operation.execute(monitorings, async=False)
kwargs = {'id__in': ids} if ids else {}
# Update used resources and trigger resource exceeded and revovery
diff --git a/orchestra/contrib/saas/apps.py b/orchestra/contrib/saas/apps.py
index bed646b0..8ad3f8c8 100644
--- a/orchestra/contrib/saas/apps.py
+++ b/orchestra/contrib/saas/apps.py
@@ -10,6 +10,4 @@ class SaaSConfig(AppConfig):
def ready(self):
from . import signals
from .models import SaaS
- services.register(SaaS)
-
-
+ services.register(SaaS, icon='saas.png')
diff --git a/orchestra/contrib/saas/backends/gitlab.py b/orchestra/contrib/saas/backends/gitlab.py
index 55c13cd5..77b8389a 100644
--- a/orchestra/contrib/saas/backends/gitlab.py
+++ b/orchestra/contrib/saas/backends/gitlab.py
@@ -12,7 +12,7 @@ class GitLabSaaSBackend(ServiceController):
verbose_name = _("GitLab SaaS")
model = 'saas.SaaS'
default_route_match = "saas.service == 'gitlab'"
- block = True
+ serialize = True
actions = ('save', 'delete', 'validate_creation')
doc_settings = (settings,
('SAAS_GITLAB_DOMAIN', 'SAAS_GITLAB_ROOT_PASSWORD'),
diff --git a/orchestra/contrib/saas/backends/phplist.py b/orchestra/contrib/saas/backends/phplist.py
index e7ed0aa0..62b8a39b 100644
--- a/orchestra/contrib/saas/backends/phplist.py
+++ b/orchestra/contrib/saas/backends/phplist.py
@@ -18,7 +18,7 @@ class PhpListSaaSBackend(ServiceController):
verbose_name = _("phpList SaaS")
model = 'saas.SaaS'
default_route_match = "saas.service == 'phplist'"
- block = True
+ serialize = True
def _save(self, saas, server):
admin_link = 'http://%s/admin/' % saas.get_site_domain()
diff --git a/orchestra/contrib/services/apps.py b/orchestra/contrib/services/apps.py
index 51f56168..43b2f55c 100644
--- a/orchestra/contrib/services/apps.py
+++ b/orchestra/contrib/services/apps.py
@@ -1,6 +1,14 @@
from django.apps import AppConfig
+from orchestra.core import administration, accounts
+from orchestra.core.translations import ModelTranslation
+
class ServicesConfig(AppConfig):
name = 'orchestra.contrib.services'
verbose_name = 'Services'
+
+ def ready(self):
+ from .models import Service
+ administration.register(Service, icon='price.png')
+ ModelTranslation.register(Service, ('description',))
diff --git a/orchestra/contrib/services/models.py b/orchestra/contrib/services/models.py
index 27daf1ab..862f026d 100644
--- a/orchestra/contrib/services/models.py
+++ b/orchestra/contrib/services/models.py
@@ -8,7 +8,6 @@ from django.utils.module_loading import autodiscover_modules
from django.utils.translation import string_concat, ugettext_lazy as _
from orchestra.core import caches, validators
-from orchestra.core.translations import ModelTranslation
from orchestra.utils.python import import_class
from . import settings
@@ -244,6 +243,3 @@ class Service(models.Model):
for instance in queryset:
updates += order_model.update_orders(instance, service=self, commit=commit)
return updates
-
-
-ModelTranslation.register(Service, ('description',))
diff --git a/orchestra/contrib/settings/admin.py b/orchestra/contrib/settings/admin.py
index 3a503591..93e44325 100644
--- a/orchestra/contrib/settings/admin.py
+++ b/orchestra/contrib/settings/admin.py
@@ -103,7 +103,5 @@ class SettingFileView(generic.TemplateView):
return context
-
admin.site.register_url(r'^settings/setting/view/$', SettingFileView.as_view(), 'settings_setting_view')
admin.site.register_url(r'^settings/setting/$', SettingView.as_view(), 'settings_setting_change')
-OrchestraIndexDashboard.register_link('Administration', 'settings_setting_change', _("Settings"))
diff --git a/orchestra/contrib/settings/apps.py b/orchestra/contrib/settings/apps.py
index 83f1b749..f93361c7 100644
--- a/orchestra/contrib/settings/apps.py
+++ b/orchestra/contrib/settings/apps.py
@@ -1,13 +1,21 @@
from django.apps import AppConfig
from django.core.checks import register, Error
from django.core.exceptions import ValidationError
+from django.utils.translation import ngettext, ugettext_lazy as _
+
+from orchestra.core import administration
from . import Setting
+
class SettingsConfig(AppConfig):
name = 'orchestra.contrib.settings'
verbose_name = 'Settings'
+ def ready(self):
+ administration.register_view('settings_setting_change', verbose_name=_("Settings"),
+ icon='Multimedia-volume-control.png')
+
@register()
def check_settings(app_configs, **kwargs):
""" perfroms all the validation """
diff --git a/orchestra/contrib/systemusers/apps.py b/orchestra/contrib/systemusers/apps.py
index 418f364e..021d92d9 100644
--- a/orchestra/contrib/systemusers/apps.py
+++ b/orchestra/contrib/systemusers/apps.py
@@ -12,7 +12,7 @@ class SystemUsersConfig(AppConfig):
def ready(self):
from .models import SystemUser
- services.register(SystemUser)
+ services.register(SystemUser, icon='roleplaying.png')
if 'migrate' in sys.argv and 'accounts' not in sys.argv:
post_migrate.connect(self.create_initial_systemuser,
dispatch_uid="orchestra.contrib.systemusers.apps.create_initial_systemuser")
diff --git a/orchestra/contrib/tasks/__init__.py b/orchestra/contrib/tasks/__init__.py
index 210d0ac6..e380d425 100644
--- a/orchestra/contrib/tasks/__init__.py
+++ b/orchestra/contrib/tasks/__init__.py
@@ -2,3 +2,6 @@ import sys
from . import settings
from .decorators import task, periodic_task, keep_state, apply_async
+
+
+default_app_config = 'orchestra.contrib.tasks.apps.TasksConfig'
diff --git a/orchestra/contrib/tasks/apps.py b/orchestra/contrib/tasks/apps.py
new file mode 100644
index 00000000..a6fd8f7e
--- /dev/null
+++ b/orchestra/contrib/tasks/apps.py
@@ -0,0 +1,14 @@
+from django.apps import AppConfig
+
+from orchestra.core import administration
+
+
+class TasksConfig(AppConfig):
+ name = 'orchestra.contrib.tasks'
+ verbose_name = "Tasks"
+
+ def ready(self):
+ from djcelery.models import PeriodicTask, TaskState, WorkerState
+ administration.register(TaskState, icon='Edit-check-sheet.png')
+ administration.register(PeriodicTask, parent=TaskState, icon='Appointment.png')
+ administration.register(WorkerState, parent=TaskState, dashboard=False)
diff --git a/orchestra/contrib/tasks/decorators.py b/orchestra/contrib/tasks/decorators.py
index c08af43e..725d4a8e 100644
--- a/orchestra/contrib/tasks/decorators.py
+++ b/orchestra/contrib/tasks/decorators.py
@@ -6,6 +6,7 @@ from threading import Thread
from celery import shared_task as celery_shared_task
from celery import states
from celery.decorators import periodic_task as celery_periodic_task
+from django.core.mail import mail_admins
from django.utils import timezone
from orchestra.utils.db import close_connection
@@ -26,11 +27,15 @@ def keep_state(fn):
result = fn(*args, **kwargs)
except Exception as exc:
state.state = states.FAILURE
- state.traceback = traceback.format_exc()
+ state.traceback = trace
state.runtime = (timezone.now()-now).total_seconds()
state.save()
+ subject = 'EXCEPTION executing task %s(args=%s, kwargs=%s)' % (name, str(args), str(kwargs))
+ trace = traceback.format_exc()
+ logger.error(subject)
+ logger.error(trace)
+ mail_admins(subject, trace)
return
- # TODO send email
else:
state.state = states.SUCCESS
state.result = str(result)
diff --git a/orchestra/contrib/tasks/management/commands/syncperiodictasks.py b/orchestra/contrib/tasks/management/commands/syncperiodictasks.py
index d638d99e..6b2b4412 100644
--- a/orchestra/contrib/tasks/management/commands/syncperiodictasks.py
+++ b/orchestra/contrib/tasks/management/commands/syncperiodictasks.py
@@ -1,2 +1,15 @@
-# create crontab entries for defines periodic tasks
+from django.core.management.base import BaseCommand, CommandError
+from djcelery.app import app
+from djcelery.schedulers import DatabaseScheduler
+
+class Command(BaseCommand):
+ help = 'Runs Orchestra method.'
+
+ def handle(self, *args, **options):
+ dbschedule = DatabaseScheduler(app=app)
+ self.stdout.write('\033[1m%i periodic tasks have been syncronized:\033[0m' % len(dbschedule.schedule))
+ size = max([len(name) for name in dbschedule.schedule])+1
+ for name, task in dbschedule.schedule.items():
+ spaces = ' '*(size-len(name))
+ self.stdout.write(' %s%s%s' % (name, spaces, task.schedule))
diff --git a/orchestra/contrib/vps/__init__.py b/orchestra/contrib/vps/__init__.py
index e69de29b..96cf9729 100644
--- a/orchestra/contrib/vps/__init__.py
+++ b/orchestra/contrib/vps/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.vps.apps.VPSConfig'
diff --git a/orchestra/contrib/vps/apps.py b/orchestra/contrib/vps/apps.py
new file mode 100644
index 00000000..919bb54d
--- /dev/null
+++ b/orchestra/contrib/vps/apps.py
@@ -0,0 +1,12 @@
+from django.apps import AppConfig
+
+from orchestra.core import services
+
+
+class VPSConfig(AppConfig):
+ name = 'orchestra.contrib.vps'
+ verbose_name = 'VPS'
+
+ def ready(self):
+ from .models import VPS
+ services.register(VPS, icon='TuxBox.png')
diff --git a/orchestra/contrib/vps/models.py b/orchestra/contrib/vps/models.py
index d647871d..27691f3c 100644
--- a/orchestra/contrib/vps/models.py
+++ b/orchestra/contrib/vps/models.py
@@ -1,8 +1,7 @@
+from django.contrib.auth.hashers import make_password
from django.db import models
from django.utils.translation import ugettext_lazy as _
-from django.contrib.auth.hashers import make_password
-from orchestra.core import services
from orchestra.core.validators import validate_hostname
from . import settings
@@ -32,6 +31,3 @@ class VPS(models.Model):
def get_username(self):
return self.hostname
-
-
-services.register(VPS)
diff --git a/orchestra/contrib/webapps/apps.py b/orchestra/contrib/webapps/apps.py
index 6f7c4650..7eb86eef 100644
--- a/orchestra/contrib/webapps/apps.py
+++ b/orchestra/contrib/webapps/apps.py
@@ -10,4 +10,4 @@ class WebAppsConfig(AppConfig):
def ready(self):
from . import signals
from .models import WebApp
- services.register(WebApp)
+ services.register(WebApp, icon='Applications-other.png')
diff --git a/orchestra/contrib/websites/__init__.py b/orchestra/contrib/websites/__init__.py
index e69de29b..93cab2d3 100644
--- a/orchestra/contrib/websites/__init__.py
+++ b/orchestra/contrib/websites/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'orchestra.contrib.websites.apps.WebsitesConfig'
diff --git a/orchestra/contrib/websites/apps.py b/orchestra/contrib/websites/apps.py
index 7451c44f..41052863 100644
--- a/orchestra/contrib/websites/apps.py
+++ b/orchestra/contrib/websites/apps.py
@@ -1,17 +1,20 @@
from django.apps import AppConfig
from django.contrib.contenttypes.fields import GenericRelation
+from orchestra.core import services
from orchestra.utils.db import database_ready
-class WebsiteConfig(AppConfig):
+class WebsitesConfig(AppConfig):
name = 'orchestra.contrib.websites'
def ready(self):
if database_ready():
- from django.contrib.contenttypes.models import ContentType
- from .models import Content
- qset = Content.content_type.field.get_limit_choices_to()
- for ct in ContentType.objects.filter(qset):
- relation = GenericRelation('websites.Content')
- ct.model_class().add_to_class('content_set', relation)
+# from django.contrib.contenttypes.models import ContentType
+# from .models import Content, Website
+# qset = Content.content_type.field.get_limit_choices_to()
+# for ct in ContentType.objects.filter(qset):
+# relation = GenericRelation('websites.Content')
+# ct.model_class().add_to_class('content_set', relation)
+ from .models import Website
+ services.register(Website, icon='Applications-internet.png')
diff --git a/orchestra/contrib/websites/models.py b/orchestra/contrib/websites/models.py
index 3ea12672..4dffd743 100644
--- a/orchestra/contrib/websites/models.py
+++ b/orchestra/contrib/websites/models.py
@@ -5,7 +5,7 @@ from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
-from orchestra.core import validators, services
+from orchestra.core import validators
from orchestra.utils.functional import cached
from . import settings
@@ -152,6 +152,3 @@ class Content(models.Model):
domain = self.website.domains.first()
if domain:
return '%s://%s%s' % (self.website.get_protocol(), domain, self.path)
-
-
-services.register(Website)
diff --git a/orchestra/core/__init__.py b/orchestra/core/__init__.py
index 0badee82..f29687bb 100644
--- a/orchestra/core/__init__.py
+++ b/orchestra/core/__init__.py
@@ -1,9 +1,12 @@
+from django.utils.translation import string_concat
+
from ..utils.python import AttrDict
class Register(object):
- def __init__(self):
+ def __init__(self, verbose_name=None):
self._registry = {}
+ self.verbose_name = verbose_name
def __contains__(self, key):
return key in self._registry
@@ -13,13 +16,21 @@ class Register(object):
def register(self, model, **kwargs):
if model in self._registry:
- raise KeyError("%s already registered" % str(model))
- plural = kwargs.get('verbose_name_plural', model._meta.verbose_name_plural)
- self._registry[model] = AttrDict(**{
- 'verbose_name': kwargs.get('verbose_name', model._meta.verbose_name),
- 'verbose_name_plural': plural,
- 'menu': kwargs.get('menu', True),
- })
+ raise KeyError("%s already registered" % model)
+ if 'verbose_name' not in kwargs:
+ kwargs['verbose_name'] = model._meta.verbose_name
+ if 'verbose_name_plural' not in kwargs:
+ kwargs['verbose_name_plural'] = model._meta.verbose_name_plural
+ self._registry[model] = AttrDict(**kwargs)
+
+ def register_view(self, view_name, **kwargs):
+ if view_name in self._registry:
+ raise KeyError("%s already registered" % view_name)
+ if 'verbose_name' not in kwargs:
+ raise KeyError("%s verbose_name is required for views" % view_name)
+ if 'verbose_name_plural' not in kwargs:
+ kwargs['verbose_name_plural'] = string_concat(kwargs['verbose_name'], 's')
+ self._registry[view_name] = AttrDict(**kwargs)
def get(self, *args):
if args:
@@ -27,7 +38,7 @@ class Register(object):
return self._registry
-services = Register()
+services = Register(verbose_name='Services')
# TODO rename to something else
-accounts = Register()
-administration = Register()
+accounts = Register(verbose_name='Accounts')
+administration = Register(verbose_name='Administration')
diff --git a/orchestra/static/orchestra/icons/Appointment.png b/orchestra/static/orchestra/icons/Appointment.png
new file mode 100644
index 00000000..06e1fe94
Binary files /dev/null and b/orchestra/static/orchestra/icons/Appointment.png differ
diff --git a/orchestra/static/orchestra/icons/Appointment.svg b/orchestra/static/orchestra/icons/Appointment.svg
new file mode 100644
index 00000000..772b37ac
--- /dev/null
+++ b/orchestra/static/orchestra/icons/Appointment.svg
@@ -0,0 +1,413 @@
+
+
+
+
diff --git a/orchestra/static/orchestra/icons/Edit-check-sheet.png b/orchestra/static/orchestra/icons/Edit-check-sheet.png
new file mode 100644
index 00000000..1fcec45a
Binary files /dev/null and b/orchestra/static/orchestra/icons/Edit-check-sheet.png differ
diff --git a/orchestra/static/orchestra/icons/Edit-check-sheet.svg b/orchestra/static/orchestra/icons/Edit-check-sheet.svg
new file mode 100644
index 00000000..e98f8210
--- /dev/null
+++ b/orchestra/static/orchestra/icons/Edit-check-sheet.svg
@@ -0,0 +1,390 @@
+
+
diff --git a/orchestra/static/orchestra/icons/Mail-send.png b/orchestra/static/orchestra/icons/Mail-send.png
new file mode 100644
index 00000000..2f8fd1f9
Binary files /dev/null and b/orchestra/static/orchestra/icons/Mail-send.png differ
diff --git a/orchestra/static/orchestra/icons/Mail-send.svg b/orchestra/static/orchestra/icons/Mail-send.svg
new file mode 100644
index 00000000..b114286a
--- /dev/null
+++ b/orchestra/static/orchestra/icons/Mail-send.svg
@@ -0,0 +1,999 @@
+
+
+
+
diff --git a/orchestra/static/orchestra/icons/Multimedia-volume-control.png b/orchestra/static/orchestra/icons/Multimedia-volume-control.png
new file mode 100644
index 00000000..9e841739
Binary files /dev/null and b/orchestra/static/orchestra/icons/Multimedia-volume-control.png differ
diff --git a/orchestra/static/orchestra/icons/Multimedia-volume-control.svg b/orchestra/static/orchestra/icons/Multimedia-volume-control.svg
new file mode 100644
index 00000000..ed02b310
--- /dev/null
+++ b/orchestra/static/orchestra/icons/Multimedia-volume-control.svg
@@ -0,0 +1,242 @@
+
+
+
+